diff --git a/Source/Charts/Charts/BarLineChartViewBase.swift b/Source/Charts/Charts/BarLineChartViewBase.swift index 747120718f..927091d1a1 100644 --- a/Source/Charts/Charts/BarLineChartViewBase.swift +++ b/Source/Charts/Charts/BarLineChartViewBase.swift @@ -426,30 +426,76 @@ open class BarLineChartViewBase: ChartViewBase, BarLineScatterCandleBubbleChartD if leftAxis.needsOffset { offsetLeft += leftAxis.requiredSize().width + if leftAxis.nameAxisEnabled + { + let nameLeftAxisHeight = leftAxis.nameAxis.size(attributes: [NSFontAttributeName: leftAxis.nameAxisFont]).height + offsetLeft += nameLeftAxisHeight + leftAxis.nameRectLeft = CGRect(x: offsetLeft - leftAxis.requiredSize().width - nameLeftAxisHeight, + y:viewPortHandler.contentTop, + width: nameLeftAxisHeight, + height:viewPortHandler.contentHeight) + } } if rightAxis.needsOffset { offsetRight += rightAxis.requiredSize().width + if rightAxis.nameAxisEnabled + { + let nameRightAxisHeight = rightAxis.nameAxis.size(attributes: [NSFontAttributeName: rightAxis.nameAxisFont]).height + offsetRight += nameRightAxisHeight + rightAxis.nameRectRight = CGRect(x: viewPortHandler.contentRight + rightAxis.requiredSize().width, + y: viewPortHandler.contentTop, + width: nameRightAxisHeight, + height: viewPortHandler.contentHeight) + } } - + if xAxis.isEnabled && xAxis.isDrawLabelsEnabled { - let xlabelheight = xAxis.labelRotatedHeight + xAxis.yOffset + + var xlabelheight = (xAxis.labelRotationAngle != 0 ? xAxis.labelRotatedHeight : xAxis.labelHeight) + xlabelheight += xAxis.yOffset + + var namexAxisHeight = CGFloat(0.0) + var nameAxisRectWidth = CGFloat(0.0) + var nameAxisRectLeft = CGFloat(0.0) + if xAxis.nameAxisEnabled + { + namexAxisHeight = xAxis.nameAxis.size(attributes: [NSFontAttributeName: xAxis.nameAxisFont]).height + nameAxisRectWidth = viewPortHandler.contentWidth + nameAxisRectLeft = viewPortHandler.contentLeft + } // offsets for x-labels if xAxis.labelPosition == .bottom { - offsetBottom += xlabelheight + offsetBottom += xlabelheight + namexAxisHeight + xAxis.nameRectBottom = CGRect(x: nameAxisRectLeft, + y: viewPortHandler.chartHeight - offsetBottom + xlabelheight, + width: nameAxisRectWidth, + height: namexAxisHeight) } else if xAxis.labelPosition == .top { - offsetTop += xlabelheight + offsetTop += xlabelheight + namexAxisHeight + xAxis.nameRectTop = CGRect(x: nameAxisRectLeft, + y: offsetTop - namexAxisHeight - xlabelheight, + width: nameAxisRectWidth, + height: namexAxisHeight) } else if xAxis.labelPosition == .bothSided { - offsetBottom += xlabelheight - offsetTop += xlabelheight + offsetBottom += xlabelheight + namexAxisHeight + xAxis.nameRectBottom = CGRect(x: nameAxisRectLeft, + y: viewPortHandler.chartHeight - offsetBottom + xlabelheight, + width: nameAxisRectWidth, + height: namexAxisHeight) + offsetTop += xlabelheight + namexAxisHeight + xAxis.nameRectTop = CGRect(x: nameAxisRectLeft, + y: offsetTop - namexAxisHeight - xlabelheight, + width: nameAxisRectWidth, + height: namexAxisHeight) } } @@ -457,7 +503,7 @@ open class BarLineChartViewBase: ChartViewBase, BarLineScatterCandleBubbleChartD offsetRight += self.extraRightOffset offsetBottom += self.extraBottomOffset offsetLeft += self.extraLeftOffset - + _viewPortHandler.restrainViewPort( offsetLeft: max(self.minOffset, offsetLeft), offsetTop: max(self.minOffset, offsetTop), @@ -469,6 +515,7 @@ open class BarLineChartViewBase: ChartViewBase, BarLineScatterCandleBubbleChartD prepareValuePxMatrix() } + /// draws the grid background internal func drawGridBackground(context: CGContext) { diff --git a/Source/Charts/Charts/HorizontalBarChartView.swift b/Source/Charts/Charts/HorizontalBarChartView.swift index 06863ee619..662943b537 100644 --- a/Source/Charts/Charts/HorizontalBarChartView.swift +++ b/Source/Charts/Charts/HorizontalBarChartView.swift @@ -46,34 +46,83 @@ open class HorizontalBarChartView: BarChartView offsetRight: &offsetRight, offsetBottom: &offsetBottom) + var nameAxisRectWidth = CGFloat(0.0) + var nameAxisRectLeft = CGFloat(0.0) + if _leftAxis.nameAxisEnabled || _rightAxis.nameAxisEnabled + { + nameAxisRectWidth = viewPortHandler.contentWidth + nameAxisRectLeft = viewPortHandler.contentLeft + } + // offsets for y-labels if _leftAxis.needsOffset { offsetTop += _leftAxis.getRequiredHeightSpace() + if leftAxis.nameAxisEnabled + { + let nameLeftAxisSize = leftAxis.nameAxis.size(attributes: [NSFontAttributeName: leftAxis.nameAxisFont]) + offsetTop += nameLeftAxisSize.height + leftAxis.nameRectTop = CGRect(x: nameAxisRectLeft, + y: offsetTop - nameLeftAxisSize.height - _leftAxis.getRequiredHeightSpace(), + width: nameAxisRectWidth, + height: nameLeftAxisSize.height) + } } if _rightAxis.needsOffset { offsetBottom += _rightAxis.getRequiredHeightSpace() + if rightAxis.nameAxisEnabled + { + let nameRightAxisSize = rightAxis.nameAxis.size(attributes: [NSFontAttributeName: rightAxis.nameAxisFont]) + offsetBottom += nameRightAxisSize.height + rightAxis.nameRectBottom = CGRect(x: nameAxisRectLeft, + y: viewPortHandler.chartHeight - offsetBottom + _rightAxis.getRequiredHeightSpace() , + width: nameAxisRectWidth, + height: nameRightAxisSize.height) + } } let xlabelwidth = _xAxis.labelRotatedWidth if _xAxis.isEnabled { + var namexAxisHeight = CGFloat(0.0) + if xAxis.nameAxisEnabled + { + namexAxisHeight = xAxis.nameAxis.size(attributes: [NSFontAttributeName: xAxis.nameAxisFont]).height + } + // offsets for x-labels if _xAxis.labelPosition == .bottom { - offsetLeft += xlabelwidth + offsetLeft += xlabelwidth + namexAxisHeight + xAxis.nameRectLeft = CGRect(x: offsetLeft - xlabelwidth - namexAxisHeight, + y:viewPortHandler.contentTop, + width: namexAxisHeight, + height:viewPortHandler.contentHeight) } else if _xAxis.labelPosition == .top { - offsetRight += xlabelwidth + offsetRight += xlabelwidth + namexAxisHeight + xAxis.nameRectRight = CGRect(x: viewPortHandler.contentRight + xlabelwidth, + y: viewPortHandler.contentTop, + width: namexAxisHeight, + height: viewPortHandler.contentHeight) } else if _xAxis.labelPosition == .bothSided { - offsetLeft += xlabelwidth - offsetRight += xlabelwidth + offsetLeft += xlabelwidth + namexAxisHeight + xAxis.nameRectLeft = CGRect(x: offsetLeft - xlabelwidth - namexAxisHeight, + y:viewPortHandler.contentTop, + width: namexAxisHeight, + height:viewPortHandler.contentHeight) + + offsetRight += xlabelwidth + namexAxisHeight + xAxis.nameRectRight = CGRect(x: viewPortHandler.contentRight + xlabelwidth, + y: viewPortHandler.contentTop, + width: namexAxisHeight, + height: viewPortHandler.contentHeight) } } diff --git a/Source/Charts/Components/AxisBase.swift b/Source/Charts/Components/AxisBase.swift index 160872e097..6b3dbcebae 100644 --- a/Source/Charts/Components/AxisBase.swift +++ b/Source/Charts/Components/AxisBase.swift @@ -41,6 +41,11 @@ open class AxisBase: ComponentBase open var drawGridLinesEnabled = true open var drawAxisLineEnabled = true + open var nameAxis : String = "" + open var nameAxisFont = NSUIFont.boldSystemFont(ofSize: 12.0) + open var nameAxisTextColor = NSUIColor.black + open var nameAxisEnabled = false + /// flag that indicates of the labels of this axis should be drawn or not open var drawLabelsEnabled = true @@ -213,6 +218,12 @@ open class AxisBase: ComponentBase /// the total range of values this axis covers open var axisRange = Double(0) + open var nameRectBottom = CGRect() + open var nameRectTop = CGRect() + open var nameRectLeft = CGRect() + open var nameRectRight = CGRect() + + /// the number of label entries the axis should have /// max = 25, /// min = 2, diff --git a/Source/Charts/Renderers/XAxisRenderer.swift b/Source/Charts/Renderers/XAxisRenderer.swift index 7bec034e63..af2968d0fd 100644 --- a/Source/Charts/Renderers/XAxisRenderer.swift +++ b/Source/Charts/Renderers/XAxisRenderer.swift @@ -101,7 +101,9 @@ open class XAxisRenderer: AxisRendererBase if xAxis.labelPosition == .top { - drawLabels(context: context, pos: viewPortHandler.contentTop - yOffset, anchor: CGPoint(x: 0.5, y: 1.0)) + let pos = viewPortHandler.contentTop - yOffset + drawLabels(context: context, pos: pos, anchor: CGPoint(x: 0.5, y: 1.0)) + drawNameXAxis(context: context, nameRect: xAxis.nameRectTop) } else if xAxis.labelPosition == .topInside { @@ -109,7 +111,9 @@ open class XAxisRenderer: AxisRendererBase } else if xAxis.labelPosition == .bottom { - drawLabels(context: context, pos: viewPortHandler.contentBottom + yOffset, anchor: CGPoint(x: 0.5, y: 0.0)) + let pos = viewPortHandler.contentBottom + yOffset + drawLabels(context: context, pos: pos, anchor: CGPoint(x: 0.5, y: 0.0)) + drawNameXAxis(context: context, nameRect: xAxis.nameRectBottom) } else if xAxis.labelPosition == .bottomInside { @@ -117,11 +121,45 @@ open class XAxisRenderer: AxisRendererBase } else { // BOTH SIDED - 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)) + // top + var pos = viewPortHandler.contentTop - yOffset + drawLabels(context: context, pos: pos, anchor: CGPoint(x: 0.5, y: 1.0)) + drawNameXAxis ( context: context, nameRect: xAxis.nameRectTop) + + // bottom + pos = viewPortHandler.contentBottom + yOffset + drawLabels(context: context, pos: pos, anchor: CGPoint(x: 0.5, y: 0.0)) + drawNameXAxis ( context: context, nameRect: xAxis.nameRectBottom) } } + /// draws the x-name + open func drawNameXAxis ( context: CGContext, nameRect: CGRect) + { + guard + let xAxis = self.axis as? XAxis + else { return } + + if xAxis.nameAxisEnabled == false + { + return + } + + #if os(OSX) + let paraStyle = NSParagraphStyle.default().mutableCopy() as! NSMutableParagraphStyle + #else + let paraStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle + #endif + + paraStyle.alignment = .center + let labelAttrs = [NSFontAttributeName: xAxis.nameAxisFont, + NSForegroundColorAttributeName: xAxis.nameAxisTextColor, + NSParagraphStyleAttributeName: paraStyle] as [String : NSObject] + let text = xAxis.nameAxis + + text.draw(in: nameRect, withAttributes: labelAttrs) + } + fileprivate var _axisLineSegmentsBuffer = [CGPoint](repeating: CGPoint(), count: 2) open override func renderAxisLine(context: CGContext) @@ -262,6 +300,47 @@ open class XAxisRenderer: AxisRendererBase } } + /// draws the x-name + open func drawNameXAxis ( + context: CGContext, + fixedPosition: CGFloat, + positions: CGPoint, + offset: CGFloat) + { + guard + let xAxis = self.axis as? XAxis + else { return } + + if xAxis.nameAxisEnabled == false + { + return + } + + #if os(OSX) + let paraStyle = NSParagraphStyle.default().mutableCopy() as! NSMutableParagraphStyle + #else + let paraStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle + #endif + + paraStyle.alignment = .center + let labelAttrs = [NSFontAttributeName: xAxis.nameAxisFont, + NSForegroundColorAttributeName: xAxis.nameAxisTextColor, + NSParagraphStyleAttributeName: paraStyle] as [String : NSObject] + let labelRotationAngleRadians = 0 * ChartUtils.Math.FDEG2RAD + + let text = xAxis.nameAxis + let labelMaxSize = CGSize() + + ChartUtils.drawMultilineText( + context: context, + text: text, + point: CGPoint(x: positions.x, y: fixedPosition), + attributes: labelAttrs, + constrainedToSize: labelMaxSize, + anchor: CGPoint(x: 0.5, y: 1.0), + angleRadians: labelRotationAngleRadians) + } + open func drawLabel( context: CGContext, formattedLabel: String, diff --git a/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift b/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift index 200fd2d9ce..aa87d32ac7 100644 --- a/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift +++ b/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift @@ -97,7 +97,9 @@ open class XAxisRendererHorizontalBarChart: XAxisRenderer if xAxis.labelPosition == .top { - drawLabels(context: context, pos: viewPortHandler.contentRight + xoffset, anchor: CGPoint(x: 0.0, y: 0.5)) + let pos = viewPortHandler.contentRight + xoffset + drawLabels(context: context, pos: pos, anchor: CGPoint(x: 0.0, y: 0.5)) + drawNameXAxis ( context: context, nameRect: xAxis.nameRectRight) } else if xAxis.labelPosition == .topInside { @@ -105,7 +107,9 @@ open class XAxisRendererHorizontalBarChart: XAxisRenderer } else if xAxis.labelPosition == .bottom { - drawLabels(context: context, pos: viewPortHandler.contentLeft - xoffset, anchor: CGPoint(x: 1.0, y: 0.5)) + let pos = viewPortHandler.contentLeft - xoffset + drawLabels(context: context, pos: pos, anchor: CGPoint(x: 1.0, y: 0.5)) + drawNameXAxis ( context: context, nameRect: xAxis.nameRectLeft) } else if xAxis.labelPosition == .bottomInside { @@ -113,9 +117,55 @@ open class XAxisRendererHorizontalBarChart: XAxisRenderer } else { // BOTH SIDED - 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)) + // right + var pos = viewPortHandler.contentRight + xoffset + drawLabels(context: context, pos: pos, anchor: CGPoint(x: 0.0, y: 0.5)) + drawNameXAxis ( context: context, nameRect: xAxis.nameRectRight ) + + // left + pos = viewPortHandler.contentLeft - xoffset + drawLabels(context: context, pos: pos, anchor: CGPoint(x: 1.0, y: 0.5)) + drawNameXAxis ( context: context, nameRect: xAxis.nameRectLeft ) + } + } + + /// draws the x-name + open override func drawNameXAxis ( context: CGContext, nameRect: CGRect) + { + guard + let xAxis = self.axis as? XAxis + else { return } + + if xAxis.nameAxisEnabled == false + { + return } + + let midY = nameRect.midY + let xNamePos = nameRect.maxX + let text = xAxis.nameAxis + + #if os(OSX) + let paraStyle = NSParagraphStyle.default().mutableCopy() as! NSMutableParagraphStyle + #else + let paraStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle + #endif + + paraStyle.alignment = .center + + let labelAttrs = [NSFontAttributeName: xAxis.nameAxisFont, + NSForegroundColorAttributeName: xAxis.nameAxisTextColor, + NSParagraphStyleAttributeName: paraStyle] as [String : NSObject] + + let labelRotationAngleRadians = -90.0 * ChartUtils.Math.FDEG2RAD + + ChartUtils.drawText( + context: context, + text: text, + point: CGPoint(x: xNamePos, y: midY), + attributes: labelAttrs, + anchor: CGPoint(x: 1.0, y: 0.5), + angleRadians: labelRotationAngleRadians) } /// draws the x-labels on the specified y-position @@ -170,6 +220,46 @@ open class XAxisRendererHorizontalBarChart: XAxisRenderer } } + /// draws the name axis + open override func drawNameXAxis( + context: CGContext, + fixedPosition: CGFloat, + positions: CGPoint, + offset: CGFloat) + { + guard + let xAxis = self.axis as? XAxis + else { return } + + if xAxis.nameAxisEnabled == false + { + return + } + + #if os(OSX) + let paraStyle = NSParagraphStyle.default().mutableCopy() as! NSMutableParagraphStyle + #else + let paraStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle + #endif + + paraStyle.alignment = .center + + let labelAttrs = [NSFontAttributeName: xAxis.nameAxisFont, + NSForegroundColorAttributeName: xAxis.nameAxisTextColor, + NSParagraphStyleAttributeName: paraStyle] as [String : NSObject] + + let labelRotationAngleRadians = -90.0 * ChartUtils.Math.FDEG2RAD + let text = xAxis.nameAxis + + ChartUtils.drawText( + context: context, + text: text, + point: CGPoint(x: fixedPosition, y: positions.y + offset), + attributes: labelAttrs, + anchor: CGPoint(x: 1.0, y: 0.5), + angleRadians: labelRotationAngleRadians) + } + open func drawLabel( context: CGContext, formattedLabel: String, diff --git a/Source/Charts/Renderers/YAxisRenderer.swift b/Source/Charts/Renderers/YAxisRenderer.swift index fd9604e868..b01c3de83a 100644 --- a/Source/Charts/Renderers/YAxisRenderer.swift +++ b/Source/Charts/Renderers/YAxisRenderer.swift @@ -53,13 +53,14 @@ open class YAxisRenderer: AxisRendererBase { textAlign = .right xPos = viewPortHandler.offsetLeft - xoffset + + drawNameYAxis( context: context, nameRect: yAxis.nameRectLeft) } else { textAlign = .left xPos = viewPortHandler.offsetLeft + xoffset } - } else { @@ -67,6 +68,8 @@ open class YAxisRenderer: AxisRendererBase { textAlign = .left xPos = viewPortHandler.contentRight + xoffset + + drawNameYAxis( context: context, nameRect: yAxis.nameRectRight) } else { @@ -83,6 +86,45 @@ open class YAxisRenderer: AxisRendererBase textAlign: textAlign) } + /// draws the name Yaxis + internal func drawNameYAxis( context: CGContext, nameRect: CGRect) + { + guard + let yAxis = self.axis as? YAxis + else { return } + + if yAxis.nameAxisEnabled == false + { + return + } + + let midY = nameRect.midY + let xNamePos = nameRect.maxX + let text = yAxis.nameAxis + + #if os(OSX) + let paraStyle = NSParagraphStyle.default().mutableCopy() as! NSMutableParagraphStyle + #else + let paraStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle + #endif + + paraStyle.alignment = .center + + let labelAttrs = [NSFontAttributeName: yAxis.nameAxisFont, + NSForegroundColorAttributeName: yAxis.nameAxisTextColor, + NSParagraphStyleAttributeName: paraStyle] as [String : NSObject] + + let labelRotationAngleRadians = -90.0 * ChartUtils.Math.FDEG2RAD + + ChartUtils.drawText( + context: context, + text: text, + point: CGPoint(x: xNamePos, y: midY), + attributes: labelAttrs, + anchor: CGPoint(x: 1.0, y: 0.5), + angleRadians: labelRotationAngleRadians) + } + open override func renderAxisLine(context: CGContext) { guard @@ -126,6 +168,41 @@ open class YAxisRenderer: AxisRendererBase context.restoreGState() } + /// draws the name Yaxis + internal func drawNameYAxis( + context: CGContext, + fixedPosition: CGFloat, + positions: CGPoint, + offset: CGFloat) + { + guard + let yAxis = self.axis as? YAxis + else { return } + + #if os(OSX) + let paraStyle = NSParagraphStyle.default().mutableCopy() as! NSMutableParagraphStyle + #else + let paraStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle + #endif + + paraStyle.alignment = .center + + let labelAttrs = [NSFontAttributeName: yAxis.nameAxisFont, + NSForegroundColorAttributeName: yAxis.nameAxisTextColor, + NSParagraphStyleAttributeName: paraStyle] as [String : NSObject] + + let labelRotationAngleRadians = -90.0 * ChartUtils.Math.FDEG2RAD + let text = yAxis.nameAxis + + ChartUtils.drawText( + context: context, + text: text, + point: CGPoint(x: fixedPosition, y: positions.y + offset), + attributes: labelAttrs, + anchor: CGPoint(x: 1.0, y: 0.5), + angleRadians: labelRotationAngleRadians) + } + /// draws the y-labels on the specified x-position internal func drawYLabels( context: CGContext, diff --git a/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift b/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift index 621d6dd6ed..d9971309b7 100644 --- a/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift +++ b/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift @@ -80,6 +80,7 @@ open class YAxisRendererHorizontalBarChart: YAxisRenderer if labelPosition == .outsideChart { yPos = viewPortHandler.contentTop - baseYOffset + drawNameYAxis( context: context, nameRect: yAxis.nameRectTop) } else { @@ -91,6 +92,7 @@ open class YAxisRendererHorizontalBarChart: YAxisRenderer if labelPosition == .outsideChart { yPos = viewPortHandler.contentBottom + lineHeight + baseYOffset + drawNameYAxis( context: context, nameRect: yAxis.nameRectBottom) } else { @@ -109,6 +111,37 @@ open class YAxisRendererHorizontalBarChart: YAxisRenderer offset: yAxis.yOffset) } + /// draws the x-name + open override func drawNameYAxis ( + context: CGContext, + nameRect: CGRect) + { + guard + let yAxis = self.axis as? YAxis + else { return } + + if yAxis.nameAxisEnabled == false + { + return + } + + let text = yAxis.nameAxis + + #if os(OSX) + let paraStyle = NSParagraphStyle.default().mutableCopy() as! NSMutableParagraphStyle + #else + let paraStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle + #endif + + paraStyle.alignment = .center + + let labelAttrs = [NSFontAttributeName: yAxis.nameAxisFont, + NSForegroundColorAttributeName: yAxis.nameAxisTextColor, + NSParagraphStyleAttributeName: paraStyle] as [String : NSObject] + + text.draw(in: nameRect, withAttributes: labelAttrs) + } + open override func renderAxisLine(context: CGContext) { guard