Skip to content

Commit

Permalink
Merge pull request #513 from danielgindi/x-axis-label-rotation
Browse files Browse the repository at this point in the history
Implemented support for rotated labels on the x-axis (Resolves #61)
  • Loading branch information
danielgindi committed Oct 27, 2015
2 parents 7302bcc + 8367365 commit 13306c3
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 57 deletions.
4 changes: 2 additions & 2 deletions Charts/Classes/Charts/BarLineChartViewBase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ public class BarLineChartViewBase: ChartViewBase, BarLineScatterCandleBubbleChar

if (xAxis.isEnabled && xAxis.isDrawLabelsEnabled)
{
let xlabelheight = xAxis.labelHeight * 2.0
let xlabelheight = xAxis.labelRotatedHeight + xAxis.yOffset

// offsets for x-labels
if (xAxis.labelPosition == .Bottom)
Expand Down Expand Up @@ -493,7 +493,7 @@ public class BarLineChartViewBase: ChartViewBase, BarLineScatterCandleBubbleChar

if (!_xAxis.isAxisModulusCustom)
{
_xAxis.axisLabelModulus = Int(ceil((CGFloat(_data.xValCount) * _xAxis.labelWidth) / (_viewPortHandler.contentWidth * _viewPortHandler.touchMatrix.a)))
_xAxis.axisLabelModulus = Int(ceil((CGFloat(_data.xValCount) * _xAxis.labelRotatedWidth) / (_viewPortHandler.contentWidth * _viewPortHandler.touchMatrix.a)))
}

if (_xAxis.axisLabelModulus < 1)
Expand Down
4 changes: 2 additions & 2 deletions Charts/Classes/Charts/HorizontalBarChartView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public class HorizontalBarChartView: BarChartView
offsetBottom += _rightAxis.getRequiredHeightSpace()
}

let xlabelwidth = _xAxis.labelWidth
let xlabelwidth = _xAxis.labelRotatedWidth

if (_xAxis.isEnabled)
{
Expand Down Expand Up @@ -130,7 +130,7 @@ public class HorizontalBarChartView: BarChartView

internal override func calcModulus()
{
_xAxis.axisLabelModulus = Int(ceil((CGFloat(_data.xValCount) * _xAxis.labelHeight) / (_viewPortHandler.contentHeight * viewPortHandler.touchMatrix.d)))
_xAxis.axisLabelModulus = Int(ceil((CGFloat(_data.xValCount) * _xAxis.labelRotatedHeight) / (_viewPortHandler.contentHeight * viewPortHandler.touchMatrix.d)))

if (_xAxis.axisLabelModulus < 1)
{
Expand Down
2 changes: 1 addition & 1 deletion Charts/Classes/Charts/PieRadarChartViewBase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ public class PieRadarChartViewBase: ChartViewBase

if x.isEnabled && x.drawLabelsEnabled
{
minOffset = max(minOffset, x.labelWidth)
minOffset = max(minOffset, x.labelRotatedWidth)
}
}

Expand Down
2 changes: 1 addition & 1 deletion Charts/Classes/Charts/RadarChartView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ public class RadarChartView: PieRadarChartViewBase

internal override var requiredBaseOffset: CGFloat
{
return _xAxis.isEnabled && _xAxis.isDrawLabelsEnabled ? _xAxis.labelWidth : 10.0
return _xAxis.isEnabled && _xAxis.isDrawLabelsEnabled ? _xAxis.labelRotatedWidth : 10.0
}

public override var radius: CGFloat
Expand Down
17 changes: 16 additions & 1 deletion Charts/Classes/Components/ChartXAxis.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,22 @@ public class ChartXAxis: ChartAxisBase
}

public var values = [String?]()

/// width of the x-axis labels in pixels - this is automatically calculated by the computeAxis() methods in the renderers
public var labelWidth = CGFloat(1.0)

/// height of the x-axis labels in pixels - this is automatically calculated by the computeAxis() methods in the renderers
public var labelHeight = CGFloat(1.0)

/// width of the (rotated) x-axis labels in pixels - this is automatically calculated by the computeAxis() methods in the renderers
public var labelRotatedWidth = CGFloat(1.0)

/// height of the (rotated) x-axis labels in pixels - this is automatically calculated by the computeAxis() methods in the renderers
public var labelRotatedHeight = CGFloat(1.0)

/// This is the angle for drawing the X axis labels (in degrees)
public var labelRotationAngle = CGFloat(0.0)

/// the space that should be left out (in characters) between the x-axis labels
/// This only applies if the number of labels that will be skipped in between drawn axis labels is not custom set.
///
Expand Down Expand Up @@ -73,7 +86,7 @@ public class ChartXAxis: ChartAxisBase
public var labelPosition = XAxisLabelPosition.Top

/// if set to true, word wrapping the labels will be enabled.
/// word wrapping is done using `(value width * labelWidth)`
/// word wrapping is done using `(value width * labelRotatedWidth)`
///
/// *Note: currently supports all charts except pie/radar/horizontal-bar*
public var wordWrapEnabled = false
Expand All @@ -90,6 +103,8 @@ public class ChartXAxis: ChartAxisBase
public override init()
{
super.init()

self.yOffset = 4.0;
}

public override func getLongestLabel() -> String
Expand Down
43 changes: 27 additions & 16 deletions Charts/Classes/Renderers/ChartXAxisRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,18 @@ public class ChartXAxisRenderer: ChartAxisRendererBase

let widthText = a as NSString

_xAxis.labelWidth = widthText.sizeWithAttributes([NSFontAttributeName: _xAxis.labelFont]).width
_xAxis.labelHeight = _xAxis.labelFont.lineHeight
let labelSize = widthText.sizeWithAttributes([NSFontAttributeName: _xAxis.labelFont])

let labelWidth = labelSize.width
let labelHeight = labelSize.height

let labelRotatedSize = ChartUtils.sizeOfRotatedRectangle(labelSize, degrees: _xAxis.labelRotationAngle)

_xAxis.labelWidth = labelWidth
_xAxis.labelHeight = labelHeight
_xAxis.labelRotatedWidth = labelRotatedSize.width
_xAxis.labelRotatedHeight = labelRotatedSize.height

_xAxis.values = xValues
}

Expand All @@ -51,28 +61,28 @@ public class ChartXAxisRenderer: ChartAxisRendererBase
return
}

let yoffset = CGFloat(4.0)
let yOffset = _xAxis.yOffset

if (_xAxis.labelPosition == .Top)
{
drawLabels(context: context, pos: viewPortHandler.offsetTop - _xAxis.labelHeight - yoffset)
drawLabels(context: context, pos: viewPortHandler.contentTop - yOffset, anchor: CGPoint(x: 0.5, y: 1.0))
}
else if (_xAxis.labelPosition == .Bottom)
else if (_xAxis.labelPosition == .TopInside)
{
drawLabels(context: context, pos: viewPortHandler.contentBottom + yoffset * 1.5)
drawLabels(context: context, pos: viewPortHandler.contentTop + yOffset + _xAxis.labelRotatedHeight, anchor: CGPoint(x: 0.5, y: 1.0))
}
else if (_xAxis.labelPosition == .BottomInside)
else if (_xAxis.labelPosition == .Bottom)
{
drawLabels(context: context, pos: viewPortHandler.contentBottom - _xAxis.labelHeight - yoffset)
drawLabels(context: context, pos: viewPortHandler.contentBottom + yOffset, anchor: CGPoint(x: 0.5, y: 0.0))
}
else if (_xAxis.labelPosition == .TopInside)
else if (_xAxis.labelPosition == .BottomInside)
{
drawLabels(context: context, pos: viewPortHandler.offsetTop + yoffset)
drawLabels(context: context, pos: viewPortHandler.contentBottom - yOffset - _xAxis.labelRotatedHeight, anchor: CGPoint(x: 0.5, y: 0.0))
}
else
{ // BOTH SIDED
drawLabels(context: context, pos: viewPortHandler.offsetTop - _xAxis.labelHeight - yoffset)
drawLabels(context: context, pos: viewPortHandler.contentBottom + yoffset * 1.6)
drawLabels(context: context, pos: viewPortHandler.contentTop - yOffset, anchor: CGPoint(x: 0.5, y: 1.0))
drawLabels(context: context, pos: viewPortHandler.contentBottom + yOffset, anchor: CGPoint(x: 0.5, y: 0.0))
}
}

Expand Down Expand Up @@ -124,14 +134,15 @@ public class ChartXAxisRenderer: ChartAxisRendererBase
}

/// draws the x-labels on the specified y-position
internal func drawLabels(context context: CGContext, pos: CGFloat)
internal func drawLabels(context context: CGContext, pos: CGFloat, anchor: CGPoint)
{
let paraStyle = NSParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle
paraStyle.alignment = .Center

let labelAttrs = [NSFontAttributeName: _xAxis.labelFont,
NSForegroundColorAttributeName: _xAxis.labelTextColor,
NSParagraphStyleAttributeName: paraStyle]
let labelRotationAngleRadians = _xAxis.labelRotationAngle * ChartUtils.Math.FDEG2RAD

let valueToPixelMatrix = transformer.valueToPixelMatrix

Expand Down Expand Up @@ -180,15 +191,15 @@ public class ChartXAxisRenderer: ChartAxisRendererBase
}
}

drawLabel(context: context, label: label!, xIndex: i, x: position.x, y: pos, align: .Center, attributes: labelAttrs, constrainedToSize: labelMaxSize)
drawLabel(context: context, label: label!, xIndex: i, x: position.x, y: pos, attributes: labelAttrs, constrainedToSize: labelMaxSize, anchor: anchor, angleRadians: labelRotationAngleRadians)
}
}
}

internal func drawLabel(context context: CGContext, label: String, xIndex: Int, x: CGFloat, y: CGFloat, align: NSTextAlignment, attributes: [String: NSObject], constrainedToSize: CGSize)
internal func drawLabel(context context: CGContext, label: String, xIndex: Int, x: CGFloat, y: CGFloat, attributes: [String: NSObject], constrainedToSize: CGSize, anchor: CGPoint, angleRadians: CGFloat)
{
let formattedLabel = _xAxis.valueFormatter?.stringForXValue(xIndex, original: label, viewPortHandler: viewPortHandler) ?? label
ChartUtils.drawMultilineText(context: context, text: formattedLabel, point: CGPoint(x: x, y: y), align: align, attributes: attributes, constrainedToSize: constrainedToSize)
ChartUtils.drawMultilineText(context: context, text: formattedLabel, point: CGPoint(x: x, y: y), attributes: attributes, constrainedToSize: constrainedToSize, anchor: anchor, angleRadians: angleRadians)
}

private var _gridLineSegmentsBuffer = [CGPoint](count: 2, repeatedValue: CGPoint())
Expand Down
5 changes: 3 additions & 2 deletions Charts/Classes/Renderers/ChartXAxisRendererBarChart.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class ChartXAxisRendererBarChart: ChartXAxisRenderer
}

/// draws the x-labels on the specified y-position
internal override func drawLabels(context context: CGContext, pos: CGFloat)
internal override func drawLabels(context context: CGContext, pos: CGFloat, anchor: CGPoint)
{
if (_chart.data === nil)
{
Expand All @@ -40,6 +40,7 @@ public class ChartXAxisRendererBarChart: ChartXAxisRenderer
let labelAttrs = [NSFontAttributeName: _xAxis.labelFont,
NSForegroundColorAttributeName: _xAxis.labelTextColor,
NSParagraphStyleAttributeName: paraStyle]
let labelRotationAngleRadians = _xAxis.labelRotationAngle * ChartUtils.Math.FDEG2RAD

let barData = _chart.data as! BarChartData
let step = barData.dataSetCount
Expand Down Expand Up @@ -99,7 +100,7 @@ public class ChartXAxisRendererBarChart: ChartXAxisRenderer
}
}

drawLabel(context: context, label: label!, xIndex: i, x: position.x, y: pos, align: .Center, attributes: labelAttrs, constrainedToSize: labelMaxSize)
drawLabel(context: context, label: label!, xIndex: i, x: position.x, y: pos, attributes: labelAttrs, constrainedToSize: labelMaxSize, anchor: anchor, angleRadians: labelRotationAngleRadians)
}
}
}
Expand Down
42 changes: 26 additions & 16 deletions Charts/Classes/Renderers/ChartXAxisRendererHorizontalBarChart.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,18 @@ public class ChartXAxisRendererHorizontalBarChart: ChartXAxisRendererBarChart
_xAxis.values = xValues

let longest = _xAxis.getLongestLabel() as NSString
let longestSize = longest.sizeWithAttributes([NSFontAttributeName: _xAxis.labelFont])
_xAxis.labelWidth = floor(longestSize.width + _xAxis.xOffset * 3.5)
_xAxis.labelHeight = longestSize.height

let labelSize = longest.sizeWithAttributes([NSFontAttributeName: _xAxis.labelFont])

let labelWidth = floor(labelSize.width + _xAxis.xOffset * 3.5)
let labelHeight = labelSize.height

let labelRotatedSize = ChartUtils.sizeOfRotatedRectangle(rectangleWidth: labelSize.width, rectangleHeight: labelHeight, degrees: _xAxis.labelRotationAngle)

_xAxis.labelWidth = labelWidth
_xAxis.labelHeight = labelHeight
_xAxis.labelRotatedWidth = round(labelRotatedSize.width + _xAxis.xOffset * 3.5)
_xAxis.labelRotatedHeight = round(labelRotatedSize.height)
}

public override func renderAxisLabels(context context: CGContext)
Expand All @@ -43,32 +52,33 @@ public class ChartXAxisRendererHorizontalBarChart: ChartXAxisRendererBarChart

if (_xAxis.labelPosition == .Top)
{
drawLabels(context: context, pos: viewPortHandler.contentRight + xoffset, align: .Left)
drawLabels(context: context, pos: viewPortHandler.contentRight + xoffset, anchor: CGPoint(x: 0.0, y: 0.5))
}
else if (_xAxis.labelPosition == .Bottom)
else if (_xAxis.labelPosition == .TopInside)
{
drawLabels(context: context, pos: viewPortHandler.contentLeft - xoffset, align: .Right)
drawLabels(context: context, pos: viewPortHandler.contentRight - xoffset, anchor: CGPoint(x: 1.0, y: 0.5))
}
else if (_xAxis.labelPosition == .BottomInside)
else if (_xAxis.labelPosition == .Bottom)
{
drawLabels(context: context, pos: viewPortHandler.contentLeft + xoffset, align: .Left)
drawLabels(context: context, pos: viewPortHandler.contentLeft - xoffset, anchor: CGPoint(x: 1.0, y: 0.5))
}
else if (_xAxis.labelPosition == .TopInside)
else if (_xAxis.labelPosition == .BottomInside)
{
drawLabels(context: context, pos: viewPortHandler.contentRight - xoffset, align: .Right)
drawLabels(context: context, pos: viewPortHandler.contentLeft + xoffset, anchor: CGPoint(x: 0.0, y: 0.5))
}
else
{ // BOTH SIDED
drawLabels(context: context, pos: viewPortHandler.contentLeft - xoffset, align: .Right)
drawLabels(context: context, pos: viewPortHandler.contentRight + xoffset, align: .Left)
drawLabels(context: context, pos: viewPortHandler.contentRight + xoffset, anchor: CGPoint(x: 0.0, y: 0.5))
drawLabels(context: context, pos: viewPortHandler.contentLeft - xoffset, anchor: CGPoint(x: 1.0, y: 0.5))
}
}

/// draws the x-labels on the specified y-position
internal func drawLabels(context context: CGContext, pos: CGFloat, align: NSTextAlignment)
internal override func drawLabels(context context: CGContext, pos: CGFloat, anchor: CGPoint)
{
let labelFont = _xAxis.labelFont
let labelTextColor = _xAxis.labelTextColor
let labelRotationAngleRadians = _xAxis.labelRotationAngle * ChartUtils.Math.FDEG2RAD

// pre allocate to save performance (dont allocate in loop)
var position = CGPoint(x: 0.0, y: 0.0)
Expand Down Expand Up @@ -98,15 +108,15 @@ public class ChartXAxisRendererHorizontalBarChart: ChartXAxisRendererBarChart

if (viewPortHandler.isInBoundsY(position.y))
{
drawLabel(context: context, label: label!, xIndex: i, x: pos, y: position.y - _xAxis.labelHeight / 2.0, align: align, attributes: [NSFontAttributeName: labelFont, NSForegroundColorAttributeName: labelTextColor])
drawLabel(context: context, label: label!, xIndex: i, x: pos, y: position.y, attributes: [NSFontAttributeName: labelFont, NSForegroundColorAttributeName: labelTextColor], anchor: anchor, angleRadians: labelRotationAngleRadians)
}
}
}

internal func drawLabel(context context: CGContext, label: String, xIndex: Int, x: CGFloat, y: CGFloat, align: NSTextAlignment, attributes: [String: NSObject])
internal func drawLabel(context context: CGContext, label: String, xIndex: Int, x: CGFloat, y: CGFloat, attributes: [String: NSObject], anchor: CGPoint, angleRadians: CGFloat)
{
let formattedLabel = _xAxis.valueFormatter?.stringForXValue(xIndex, original: label, viewPortHandler: viewPortHandler) ?? label
ChartUtils.drawText(context: context, text: formattedLabel, point: CGPoint(x: x, y: y), align: align, attributes: attributes)
ChartUtils.drawText(context: context, text: formattedLabel, point: CGPoint(x: x, y: y), attributes: attributes, anchor: anchor, angleRadians: angleRadians)
}

private var _gridLineSegmentsBuffer = [CGPoint](count: 2, repeatedValue: CGPoint())
Expand Down
10 changes: 6 additions & 4 deletions Charts/Classes/Renderers/ChartXAxisRendererRadarChart.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public class ChartXAxisRendererRadarChart: ChartXAxisRenderer

let labelFont = _xAxis.labelFont
let labelTextColor = _xAxis.labelTextColor
let labelRotationAngleRadians = _xAxis.labelRotationAngle * ChartUtils.Math.FDEG2RAD
let drawLabelAnchor = CGPoint(x: 0.5, y: 0.0)

let sliceangle = _chart.sliceAngle

Expand All @@ -55,16 +57,16 @@ public class ChartXAxisRendererRadarChart: ChartXAxisRenderer

let angle = (sliceangle * CGFloat(i) + _chart.rotationAngle) % 360.0

let p = ChartUtils.getPosition(center: center, dist: CGFloat(_chart.yRange) * factor + _xAxis.labelWidth / 2.0, angle: angle)
let p = ChartUtils.getPosition(center: center, dist: CGFloat(_chart.yRange) * factor + _xAxis.labelRotatedWidth / 2.0, angle: angle)

drawLabel(context: context, label: label!, xIndex: i, x: p.x, y: p.y - _xAxis.labelHeight / 2.0, align: .Center, attributes: [NSFontAttributeName: labelFont, NSForegroundColorAttributeName: labelTextColor])
drawLabel(context: context, label: label!, xIndex: i, x: p.x, y: p.y - _xAxis.labelRotatedHeight / 2.0, attributes: [NSFontAttributeName: labelFont, NSForegroundColorAttributeName: labelTextColor], anchor: drawLabelAnchor, angleRadians: labelRotationAngleRadians)
}
}

internal func drawLabel(context context: CGContext, label: String, xIndex: Int, x: CGFloat, y: CGFloat, align: NSTextAlignment, attributes: [String: NSObject])
internal func drawLabel(context context: CGContext, label: String, xIndex: Int, x: CGFloat, y: CGFloat, attributes: [String: NSObject], anchor: CGPoint, angleRadians: CGFloat)
{
let formattedLabel = _xAxis.valueFormatter?.stringForXValue(xIndex, original: label, viewPortHandler: viewPortHandler) ?? label
ChartUtils.drawText(context: context, text: formattedLabel, point: CGPoint(x: x, y: y), align: align, attributes: attributes)
ChartUtils.drawText(context: context, text: formattedLabel, point: CGPoint(x: x, y: y), attributes: attributes, anchor: anchor, angleRadians: angleRadians)
}

public override func renderLimitLines(context context: CGContext)
Expand Down
Loading

0 comments on commit 13306c3

Please sign in to comment.