Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A simple drop-in replacement for rounding bar charts corner in Swift Charts library
//
// BarChartRenderer.swift
// Charts
//
// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda
// A port of MPAndroidChart for iOS
// Licensed under Apache License 2.0
//
// https://github.com/danielgindi/Charts
//
import Foundation
import CoreGraphics
#if !os(OSX)
import UIKit
#endif
open class BarChartRenderer: BarLineScatterCandleBubbleRenderer
{
/// A nested array of elements ordered logically (i.e not in visual/drawing order) for use with VoiceOver
///
/// Its use is apparent when there are multiple data sets, since we want to read bars in left to right order,
/// irrespective of dataset. However, drawing is done per dataset, so using this array and then flattening it prevents us from needing to
/// re-render for the sake of accessibility.
///
/// In practise, its structure is:
///
/// ````
/// [
/// [dataset1 element1, dataset2 element1],
/// [dataset1 element2, dataset2 element2],
/// [dataset1 element3, dataset2 element3]
/// ...
/// ]
/// ````
/// This is done to provide numerical inference across datasets to a screenreader user, in the same way that a sighted individual
/// uses a multi-dataset bar chart.
///
/// The ````internal```` specifier is to allow subclasses (HorizontalBar) to populate the same array
internal lazy var accessibilityOrderedElements: [[NSUIAccessibilityElement]] = accessibilityCreateEmptyOrderedElements()
internal let barCornerRadius = CGFloat(5.0)
private class Buffer
{
var rects = [CGRect]()
}
@objc open weak var dataProvider: BarChartDataProvider?
@objc public init(dataProvider: BarChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler)
{
super.init(animator: animator, viewPortHandler: viewPortHandler)
self.dataProvider = dataProvider
}
// [CGRect] per dataset
private var _buffers = [Buffer]()
open override func initBuffers()
{
if let barData = dataProvider?.barData
{
// Matche buffers count to dataset count
if _buffers.count != barData.dataSetCount
{
while _buffers.count < barData.dataSetCount
{
_buffers.append(Buffer())
}
while _buffers.count > barData.dataSetCount
{
_buffers.removeLast()
}
}
for i in stride(from: 0, to: barData.dataSetCount, by: 1)
{
let set = barData.dataSets[i] as! IBarChartDataSet
let size = set.entryCount * (set.isStacked ? set.stackSize : 1)
if _buffers[i].rects.count != size
{
_buffers[i].rects = [CGRect](repeating: CGRect(), count: size)
}
}
}
else
{
_buffers.removeAll()
}
}
private func prepareBuffer(dataSet: IBarChartDataSet, index: Int)
{
guard
let dataProvider = dataProvider,
let barData = dataProvider.barData
else { return }
let barWidthHalf = barData.barWidth / 2.0
let buffer = _buffers[index]
var bufferIndex = 0
let containsStacks = dataSet.isStacked
let isInverted = dataProvider.isInverted(axis: dataSet.axisDependency)
let phaseY = animator.phaseY
var barRect = CGRect()
var x: Double
var y: Double
for i in stride(from: 0, to: min(Int(ceil(Double(dataSet.entryCount) * animator.phaseX)), dataSet.entryCount), by: 1)
{
guard let e = dataSet.entryForIndex(i) as? BarChartDataEntry else { continue }
let vals = e.yValues
x = e.x
y = e.y
if !containsStacks || vals == nil
{
let left = CGFloat(x - barWidthHalf)
let right = CGFloat(x + barWidthHalf)
var top = isInverted
? (y <= 0.0 ? CGFloat(y) : 0)
: (y >= 0.0 ? CGFloat(y) : 0)
var bottom = isInverted
? (y >= 0.0 ? CGFloat(y) : 0)
: (y <= 0.0 ? CGFloat(y) : 0)
/* When drawing each bar, the renderer actually draws each bar from 0 to the required value.
* This drawn bar is then clipped to the visible chart rect in BarLineChartViewBase's draw(rect:) using clipDataToContent.
* While this works fine when calculating the bar rects for drawing, it causes the accessibilityFrames to be oversized in some cases.
* This offset attempts to undo that unnecessary drawing when calculating barRects
*
* +---------------------------------------------------------------+---------------------------------------------------------------+
* | Situation 1: (!inverted && y >= 0) | Situation 3: (inverted && y >= 0) |
* | | |
* | y -> +--+ <- top | 0 -> ---+--+---+--+------ <- top |
* | |//| } topOffset = y - max | | | |//| } topOffset = min |
* | max -> +---------+--+----+ <- top - topOffset | min -> +--+--+---+--+----+ <- top + topOffset |
* | | +--+ |//| | | | | | |//| | |
* | | | | |//| | | | +--+ |//| | |
* | | | | |//| | | | |//| | |
* | min -> +--+--+---+--+----+ <- bottom + bottomOffset | max -> +---------+--+----+ <- bottom - bottomOffset |
* | | | |//| } bottomOffset = min | |//| } bottomOffset = y - max |
* | 0 -> ---+--+---+--+----- <- bottom | y -> +--+ <- bottom |
* | | |
* +---------------------------------------------------------------+---------------------------------------------------------------+
* | Situation 2: (!inverted && y < 0) | Situation 4: (inverted && y < 0) |
* | | |
* | 0 -> ---+--+---+--+----- <- top | y -> +--+ <- top |
* | | | |//| } topOffset = -max | |//| } topOffset = min - y |
* | max -> +--+--+---+--+----+ <- top - topOffset | min -> +---------+--+----+ <- top + topOffset |
* | | | | |//| | | | +--+ |//| | |
* | | +--+ |//| | | | | | |//| | |
* | | |//| | | | | | |//| | |
* | min -> +---------+--+----+ <- bottom + bottomOffset | max -> +--+--+---+--+----+ <- bottom - bottomOffset |
* | |//| } bottomOffset = min - y | | | |//| } bottomOffset = -max |
* | y -> +--+ <- bottom | 0 -> ---+--+---+--+------- <- bottom |
* | | |
* +---------------------------------------------------------------+---------------------------------------------------------------+
*/
var topOffset: CGFloat = 0.0
var bottomOffset: CGFloat = 0.0
if let offsetView = dataProvider as? BarChartView
{
let offsetAxis = offsetView.getAxis(dataSet.axisDependency)
if y >= 0
{
// situation 1
if offsetAxis.axisMaximum < y
{
topOffset = CGFloat(y - offsetAxis.axisMaximum)
}
if offsetAxis.axisMinimum > 0
{
bottomOffset = CGFloat(offsetAxis.axisMinimum)
}
}
else // y < 0
{
//situation 2
if offsetAxis.axisMaximum < 0
{
topOffset = CGFloat(offsetAxis.axisMaximum * -1)
}
if offsetAxis.axisMinimum > y
{
bottomOffset = CGFloat(offsetAxis.axisMinimum - y)
}
}
if isInverted
{
// situation 3 and 4
// exchange topOffset/bottomOffset based on 1 and 2
// see diagram above
(topOffset, bottomOffset) = (bottomOffset, topOffset)
}
}
//apply offset
top = isInverted ? top + topOffset : top - topOffset
bottom = isInverted ? bottom - bottomOffset : bottom + bottomOffset
// multiply the height of the rect with the phase
// explicitly add 0 + topOffset to indicate this is changed after adding accessibility support (#3650, #3520)
if top > 0 + topOffset
{
top *= CGFloat(phaseY)
}
else
{
bottom *= CGFloat(phaseY)
}
barRect.origin.x = left
barRect.origin.y = top
barRect.size.width = right - left
barRect.size.height = bottom - top
buffer.rects[bufferIndex] = barRect
bufferIndex += 1
}
else
{
var posY = 0.0
var negY = -e.negativeSum
var yStart = 0.0
// fill the stack
for k in 0 ..< vals!.count
{
let value = vals![k]
if value == 0.0 && (posY == 0.0 || negY == 0.0)
{
// Take care of the situation of a 0.0 value, which overlaps a non-zero bar
y = value
yStart = y
}
else if value >= 0.0
{
y = posY
yStart = posY + value
posY = yStart
}
else
{
y = negY
yStart = negY + abs(value)
negY += abs(value)
}
let left = CGFloat(x - barWidthHalf)
let right = CGFloat(x + barWidthHalf)
var top = isInverted
? (y <= yStart ? CGFloat(y) : CGFloat(yStart))
: (y >= yStart ? CGFloat(y) : CGFloat(yStart))
var bottom = isInverted
? (y >= yStart ? CGFloat(y) : CGFloat(yStart))
: (y <= yStart ? CGFloat(y) : CGFloat(yStart))
// multiply the height of the rect with the phase
top *= CGFloat(phaseY)
bottom *= CGFloat(phaseY)
barRect.origin.x = left
barRect.size.width = right - left
barRect.origin.y = top
barRect.size.height = bottom - top
buffer.rects[bufferIndex] = barRect
bufferIndex += 1
}
}
}
}
open override func drawData(context: CGContext)
{
guard
let dataProvider = dataProvider,
let barData = dataProvider.barData
else { return }
// If we redraw the data, remove and repopulate accessible elements to update label values and frames
accessibleChartElements.removeAll()
accessibilityOrderedElements = accessibilityCreateEmptyOrderedElements()
// Make the chart header the first element in the accessible elements array
if let chart = dataProvider as? BarChartView {
let element = createAccessibleHeader(usingChart: chart,
andData: barData,
withDefaultDescription: "Bar Chart")
accessibleChartElements.append(element)
}
// Populate logically ordered nested elements into accessibilityOrderedElements in drawDataSet()
for i in 0 ..< barData.dataSetCount
{
guard let set = barData.getDataSetByIndex(i) else { continue }
if set.isVisible
{
if !(set is IBarChartDataSet)
{
fatalError("Datasets for BarChartRenderer must conform to IBarChartDataset")
}
drawDataSet(context: context, dataSet: set as! IBarChartDataSet, index: i)
}
}
// Merge nested ordered arrays into the single accessibleChartElements.
accessibleChartElements.append(contentsOf: accessibilityOrderedElements.flatMap { $0 } )
accessibilityPostLayoutChangedNotification()
}
private var _barShadowRectBuffer: CGRect = CGRect()
@objc open func drawDataSet(context: CGContext, dataSet: IBarChartDataSet, index: Int)
{
guard let dataProvider = dataProvider else { return }
let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency)
prepareBuffer(dataSet: dataSet, index: index)
trans.rectValuesToPixel(&_buffers[index].rects)
let borderWidth = dataSet.barBorderWidth
let borderColor = dataSet.barBorderColor
let drawBorder = borderWidth > 0.0
context.saveGState()
// draw the bar shadow before the values
if dataProvider.isDrawBarShadowEnabled
{
guard let barData = dataProvider.barData else { return }
let barWidth = barData.barWidth
let barWidthHalf = barWidth / 2.0
var x: Double = 0.0
for i in stride(from: 0, to: min(Int(ceil(Double(dataSet.entryCount) * animator.phaseX)), dataSet.entryCount), by: 1)
{
guard let e = dataSet.entryForIndex(i) as? BarChartDataEntry else { continue }
x = e.x
_barShadowRectBuffer.origin.x = CGFloat(x - barWidthHalf)
_barShadowRectBuffer.size.width = CGFloat(barWidth)
trans.rectValueToPixel(&_barShadowRectBuffer)
if !viewPortHandler.isInBoundsLeft(_barShadowRectBuffer.origin.x + _barShadowRectBuffer.size.width)
{
continue
}
if !viewPortHandler.isInBoundsRight(_barShadowRectBuffer.origin.x)
{
break
}
_barShadowRectBuffer.origin.y = viewPortHandler.contentTop
_barShadowRectBuffer.size.height = viewPortHandler.contentHeight
context.setFillColor(dataSet.barShadowColor.cgColor)
context.fill(_barShadowRectBuffer)
}
}
let buffer = _buffers[index]
// draw the bar shadow before the values
if dataProvider.isDrawBarShadowEnabled
{
for j in stride(from: 0, to: buffer.rects.count, by: 1)
{
let barRect = buffer.rects[j]
if (!viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width))
{
continue
}
if (!viewPortHandler.isInBoundsRight(barRect.origin.x))
{
break
}
context.setFillColor(dataSet.barShadowColor.cgColor)
let bezierPath = UIBezierPath(roundedRect: barRect, cornerRadius: barCornerRadius)
context.addPath(bezierPath.cgPath)
context.drawPath(using: .fill)
}
}
let isSingleColor = dataSet.colors.count == 1
if isSingleColor
{
context.setFillColor(dataSet.color(atIndex: 0).cgColor)
}
// In case the chart is stacked, we need to accomodate individual bars within accessibilityOrdereredElements
let isStacked = dataSet.isStacked
let stackSize = isStacked ? dataSet.stackSize : 1
for j in stride(from: 0, to: buffer.rects.count, by: 1)
{
let barRect = buffer.rects[j]
if (!viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width))
{
continue
}
if (!viewPortHandler.isInBoundsRight(barRect.origin.x))
{
break
}
if !isSingleColor
{
// Set the color for the currently drawn value. If the index is out of bounds, reuse colors.
context.setFillColor(dataSet.color(atIndex: j).cgColor)
}
let bezierPath = UIBezierPath(roundedRect: barRect, cornerRadius: barCornerRadius)
context.addPath(bezierPath.cgPath)
context.drawPath(using: .fill)
if drawBorder
{
context.setStrokeColor(borderColor.cgColor)
context.setLineWidth(borderWidth)
context.stroke(barRect)
}
// Create and append the corresponding accessibility element to accessibilityOrderedElements
if let chart = dataProvider as? BarChartView
{
let element = createAccessibleElement(withIndex: j,
container: chart,
dataSet: dataSet,
dataSetIndex: index,
stackSize: stackSize)
{ (element) in
element.accessibilityFrame = barRect
}
accessibilityOrderedElements[j/stackSize].append(element)
}
}
context.restoreGState()
}
open func prepareBarHighlight(
x: Double,
y1: Double,
y2: Double,
barWidthHalf: Double,
trans: Transformer,
rect: inout CGRect)
{
let left = x - barWidthHalf
let right = x + barWidthHalf
let top = y1
let bottom = y2
rect.origin.x = CGFloat(left)
rect.origin.y = CGFloat(top)
rect.size.width = CGFloat(right - left)
rect.size.height = CGFloat(bottom - top)
trans.rectValueToPixel(&rect, phaseY: animator.phaseY )
}
open override func drawValues(context: CGContext)
{
// if values are drawn
if isDrawingValuesAllowed(dataProvider: dataProvider)
{
guard
let dataProvider = dataProvider,
let barData = dataProvider.barData
else { return }
var dataSets = barData.dataSets
let valueOffsetPlus: CGFloat = 4.5
var posOffset: CGFloat
var negOffset: CGFloat
let drawValueAboveBar = dataProvider.isDrawValueAboveBarEnabled
for dataSetIndex in 0 ..< barData.dataSetCount
{
guard let dataSet = dataSets[dataSetIndex] as? IBarChartDataSet else { continue }
if !shouldDrawValues(forDataSet: dataSet)
{
continue
}
let isInverted = dataProvider.isInverted(axis: dataSet.axisDependency)
// calculate the correct offset depending on the draw position of the value
let valueFont = dataSet.valueFont
let valueTextHeight = valueFont.lineHeight
posOffset = (drawValueAboveBar ? -(valueTextHeight + valueOffsetPlus) : valueOffsetPlus)
negOffset = (drawValueAboveBar ? valueOffsetPlus : -(valueTextHeight + valueOffsetPlus))
if isInverted
{
posOffset = -posOffset - valueTextHeight
negOffset = -negOffset - valueTextHeight
}
let buffer = _buffers[dataSetIndex]
guard let formatter = dataSet.valueFormatter else { continue }
let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency)
let phaseY = animator.phaseY
let iconsOffset = dataSet.iconsOffset
// if only single values are drawn (sum)
if !dataSet.isStacked
{
for j in 0 ..< Int(ceil(Double(dataSet.entryCount) * animator.phaseX))
{
guard let e = dataSet.entryForIndex(j) as? BarChartDataEntry else { continue }
let rect = buffer.rects[j]
let x = rect.origin.x + rect.size.width / 2.0
if !viewPortHandler.isInBoundsRight(x)
{
break
}
if !viewPortHandler.isInBoundsY(rect.origin.y)
|| !viewPortHandler.isInBoundsLeft(x)
{
continue
}
let val = e.y
if dataSet.isDrawValuesEnabled
{
drawValue(
context: context,
value: formatter.stringForValue(
val,
entry: e,
dataSetIndex: dataSetIndex,
viewPortHandler: viewPortHandler),
xPos: x,
yPos: val >= 0.0
? (rect.origin.y + posOffset)
: (rect.origin.y + rect.size.height + negOffset),
font: valueFont,
align: .center,
color: dataSet.valueTextColorAt(j))
}
if let icon = e.icon, dataSet.isDrawIconsEnabled
{
var px = x
var py = val >= 0.0
? (rect.origin.y + posOffset)
: (rect.origin.y + rect.size.height + negOffset)
px += iconsOffset.x
py += iconsOffset.y
ChartUtils.drawImage(
context: context,
image: icon,
x: px,
y: py,
size: icon.size)
}
}
}
else
{
// if we have stacks
var bufferIndex = 0
for index in 0 ..< Int(ceil(Double(dataSet.entryCount) * animator.phaseX))
{
guard let e = dataSet.entryForIndex(index) as? BarChartDataEntry else { continue }
let vals = e.yValues
let rect = buffer.rects[bufferIndex]
let x = rect.origin.x + rect.size.width / 2.0
// we still draw stacked bars, but there is one non-stacked in between
if vals == nil
{
if !viewPortHandler.isInBoundsRight(x)
{
break
}
if !viewPortHandler.isInBoundsY(rect.origin.y)
|| !viewPortHandler.isInBoundsLeft(x)
{
continue
}
if dataSet.isDrawValuesEnabled
{
drawValue(
context: context,
value: formatter.stringForValue(
e.y,
entry: e,
dataSetIndex: dataSetIndex,
viewPortHandler: viewPortHandler),
xPos: x,
yPos: rect.origin.y +
(e.y >= 0 ? posOffset : negOffset),
font: valueFont,
align: .center,
color: dataSet.valueTextColorAt(index))
}
if let icon = e.icon, dataSet.isDrawIconsEnabled
{
var px = x
var py = rect.origin.y +
(e.y >= 0 ? posOffset : negOffset)
px += iconsOffset.x
py += iconsOffset.y
ChartUtils.drawImage(
context: context,
image: icon,
x: px,
y: py,
size: icon.size)
}
}
else
{
// draw stack values
let vals = vals!
var transformed = [CGPoint]()
var posY = 0.0
var negY = -e.negativeSum
for k in 0 ..< vals.count
{
let value = vals[k]
var y: Double
if value == 0.0 && (posY == 0.0 || negY == 0.0)
{
// Take care of the situation of a 0.0 value, which overlaps a non-zero bar
y = value
}
else if value >= 0.0
{
posY += value
y = posY
}
else
{
y = negY
negY -= value
}
transformed.append(CGPoint(x: 0.0, y: CGFloat(y * phaseY)))
}
trans.pointValuesToPixel(&transformed)
for k in 0 ..< transformed.count
{
let val = vals[k]
let drawBelow = (val == 0.0 && negY == 0.0 && posY > 0.0) || val < 0.0
let y = transformed[k].y + (drawBelow ? negOffset : posOffset)
if !viewPortHandler.isInBoundsRight(x)
{
break
}
if !viewPortHandler.isInBoundsY(y) || !viewPortHandler.isInBoundsLeft(x)
{
continue
}
if dataSet.isDrawValuesEnabled
{
drawValue(
context: context,
value: formatter.stringForValue(
vals[k],
entry: e,
dataSetIndex: dataSetIndex,
viewPortHandler: viewPortHandler),
xPos: x,
yPos: y,
font: valueFont,
align: .center,
color: dataSet.valueTextColorAt(index))
}
if let icon = e.icon, dataSet.isDrawIconsEnabled
{
ChartUtils.drawImage(
context: context,
image: icon,
x: x + iconsOffset.x,
y: y + iconsOffset.y,
size: icon.size)
}
}
}
bufferIndex = vals == nil ? (bufferIndex + 1) : (bufferIndex + vals!.count)
}
}
}
}
}
/// Draws a value at the specified x and y position.
@objc open func drawValue(context: CGContext, value: String, xPos: CGFloat, yPos: CGFloat, font: NSUIFont, align: NSTextAlignment, color: NSUIColor)
{
ChartUtils.drawText(context: context, text: value, point: CGPoint(x: xPos, y: yPos), align: align, attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: color])
}
open override func drawExtras(context: CGContext)
{
}
open override func drawHighlighted(context: CGContext, indices: [Highlight])
{
guard
let dataProvider = dataProvider,
let barData = dataProvider.barData
else { return }
context.saveGState()
var barRect = CGRect()
for high in indices
{
guard
let set = barData.getDataSetByIndex(high.dataSetIndex) as? IBarChartDataSet,
set.isHighlightEnabled
else { continue }
if let e = set.entryForXValue(high.x, closestToY: high.y) as? BarChartDataEntry
{
if !isInBoundsX(entry: e, dataSet: set)
{
continue
}
let trans = dataProvider.getTransformer(forAxis: set.axisDependency)
context.setFillColor(set.highlightColor.cgColor)
context.setAlpha(set.highlightAlpha)
let isStack = high.stackIndex >= 0 && e.isStacked
let y1: Double
let y2: Double
if isStack
{
if dataProvider.isHighlightFullBarEnabled
{
y1 = e.positiveSum
y2 = -e.negativeSum
}
else
{
let range = e.ranges?[high.stackIndex]
y1 = range?.from ?? 0.0
y2 = range?.to ?? 0.0
}
}
else
{
y1 = e.y
y2 = 0.0
}
prepareBarHighlight(x: e.x, y1: y1, y2: y2, barWidthHalf: barData.barWidth / 2.0, trans: trans, rect: &barRect)
setHighlightDrawPos(highlight: high, barRect: barRect)
let bezierPath = UIBezierPath(roundedRect: barRect, cornerRadius: barCornerRadius)
context.addPath(bezierPath.cgPath)
context.drawPath(using: .fill)
}
}
context.restoreGState()
}
/// Sets the drawing position of the highlight object based on the given bar-rect.
internal func setHighlightDrawPos(highlight high: Highlight, barRect: CGRect)
{
high.setDraw(x: barRect.midX, y: barRect.origin.y)
}
/// Creates a nested array of empty subarrays each of which will be populated with NSUIAccessibilityElements.
/// This is marked internal to support HorizontalBarChartRenderer as well.
internal func accessibilityCreateEmptyOrderedElements() -> [[NSUIAccessibilityElement]]
{
guard let chart = dataProvider as? BarChartView else { return [] }
// Unlike Bubble & Line charts, here we use the maximum entry count to account for stacked bars
let maxEntryCount = chart.data?.maxEntryCountSet?.entryCount ?? 0
return Array(repeating: [NSUIAccessibilityElement](),
count: maxEntryCount)
}
/// Creates an NSUIAccessibleElement representing the smallest meaningful bar of the chart
/// i.e. in case of a stacked chart, this returns each stack, not the combined bar.
/// Note that it is marked internal to support subclass modification in the HorizontalBarChart.
internal func createAccessibleElement(withIndex idx: Int,
container: BarChartView,
dataSet: IBarChartDataSet,
dataSetIndex: Int,
stackSize: Int,
modifier: (NSUIAccessibilityElement) -> ()) -> NSUIAccessibilityElement
{
let element = NSUIAccessibilityElement(accessibilityContainer: container)
let xAxis = container.xAxis
guard let e = dataSet.entryForIndex(idx/stackSize) as? BarChartDataEntry else { return element }
guard let dataProvider = dataProvider else { return element }
// NOTE: The formatter can cause issues when the x-axis labels are consecutive ints.
// i.e. due to the Double conversion, if there are more than one data set that are grouped,
// there is the possibility of some labels being rounded up. A floor() might fix this, but seems to be a brute force solution.
let label = xAxis.valueFormatter?.stringForValue(e.x, axis: xAxis) ?? "\(e.x)"
var elementValueText = dataSet.valueFormatter?.stringForValue(
e.y,
entry: e,
dataSetIndex: dataSetIndex,
viewPortHandler: viewPortHandler) ?? "\(e.y)"
if dataSet.isStacked, let vals = e.yValues
{
let labelCount = min(dataSet.colors.count, stackSize)
let stackLabel: String?
if (dataSet.stackLabels.count > 0 && labelCount > 0) {
let labelIndex = idx % labelCount
stackLabel = dataSet.stackLabels.indices.contains(labelIndex) ? dataSet.stackLabels[labelIndex] : nil
} else {
stackLabel = nil
}
elementValueText = dataSet.valueFormatter?.stringForValue(
vals[idx % stackSize],
entry: e,
dataSetIndex: dataSetIndex,
viewPortHandler: viewPortHandler) ?? "\(e.y)"
if let stackLabel = stackLabel {
elementValueText = stackLabel + " \(elementValueText)"
} else {
elementValueText = "\(elementValueText)"
}
}
let dataSetCount = dataProvider.barData?.dataSetCount ?? -1
let doesContainMultipleDataSets = dataSetCount > 1
element.accessibilityLabel = "\(doesContainMultipleDataSets ? (dataSet.label ?? "") + ", " : "") \(label): \(elementValueText)"
modifier(element)
return element
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment