Skip to content

Commit

Permalink
Added feature highlightFullBar for highlighting all values at xIndex
Browse files Browse the repository at this point in the history
This also provides backwards-compatibility for old behaviour of Combined chart

NOTE: The drawHighlight code was just indented and wrapped in a loop over datasets.
  • Loading branch information
danielgindi committed May 8, 2016
1 parent 8f82f60 commit b08ec80
Show file tree
Hide file tree
Showing 9 changed files with 316 additions and 268 deletions.
6 changes: 6 additions & 0 deletions Charts/Classes/Charts/BarLineChartViewBase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1595,6 +1595,12 @@ public class BarLineChartViewBase: ChartViewBase, BarLineScatterCandleBubbleChar
return highlightPerDragEnabled
}

/// Set this to `true` to make the highlight full-bar oriented, `false` to make it highlight single values
public var highlightFullBarEnabled: Bool = false

/// - returns: true the highlight is be full-bar oriented, false if single-value
public var isHighlightFullBarEnabled: Bool { return highlightFullBarEnabled }

/// **default**: true
/// - returns: true if drawing the grid background is enabled, false if not.
public var isDrawGridBackgroundEnabled: Bool
Expand Down
11 changes: 7 additions & 4 deletions Charts/Classes/Charts/ChartViewBase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -489,16 +489,19 @@ public class ChartViewBase: NSUIView, ChartDataProvider, ChartAnimatorDelegate
{
// set the indices to highlight
entry = _data?.getEntryForHighlight(h!)
if (entry == nil ||
entry?.xIndex != h?.xIndex ||
(entry?.value != h!.value && !isnan(h!.value)))
if (entry == nil)
{
h = nil
entry = nil
_indicesToHighlight.removeAll(keepCapacity: false)
}
else
{
if self is BarLineChartViewBase
&& (self as! BarLineChartViewBase).isHighlightFullBarEnabled
{
h = ChartHighlight(xIndex: h!.xIndex, value: Double.NaN, dataIndex: -1, dataSetIndex: -1, stackIndex: -1)
}

_indicesToHighlight = [h!]
}
}
Expand Down
3 changes: 3 additions & 0 deletions Charts/Classes/Charts/CombinedChartView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public class CombinedChartView: BarLineChartViewBase, LineChartDataProvider, Bar

self.highlighter = CombinedHighlighter(chart: self)

// Old default behaviour
self.highlightFullBarEnabled = true

/// WORKAROUND: Swift 2.0 compiler malfunctions when optimizations are enabled, and assigning directly to _fillFormatter causes a crash with a EXC_BAD_ACCESS. See https://github.com/danielgindi/Charts/issues/406
let workaroundFormatter = ChartDefaultFillFormatter()
_fillFormatter = workaroundFormatter
Expand Down
142 changes: 73 additions & 69 deletions Charts/Classes/Renderers/BarChartRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -489,96 +489,100 @@ public class BarChartRenderer: ChartDataRendererBase
let drawHighlightArrowEnabled = dataProvider.isDrawHighlightArrowEnabled
var barRect = CGRect()

for i in 0 ..< indices.count
for high in indices
{
let h = indices[i]
let index = h.xIndex
let minDataSetIndex = high.dataSetIndex == -1 ? 0 : high.dataSetIndex
let maxDataSetIndex = high.dataSetIndex == -1 ? barData.dataSetCount : (high.dataSetIndex + 1)
if maxDataSetIndex - minDataSetIndex < 1 { continue }

let dataSetIndex = h.dataSetIndex

guard let set = barData.getDataSetByIndex(dataSetIndex) as? IBarChartDataSet else { continue }

if (!set.isHighlightEnabled)
{
continue
}

let barspaceHalf = set.barSpace / 2.0

let trans = dataProvider.getTransformer(set.axisDependency)

CGContextSetFillColorWithColor(context, set.highlightColor.CGColor)
CGContextSetAlpha(context, set.highlightAlpha)

// check outofbounds
if (CGFloat(index) < (CGFloat(dataProvider.chartXMax) * animator.phaseX) / CGFloat(setCount))
for dataSetIndex in minDataSetIndex..<maxDataSetIndex
{
let e = set.entryForXIndex(index) as! BarChartDataEntry!
guard let set = barData.getDataSetByIndex(dataSetIndex) as? IBarChartDataSet else { continue }

if (e === nil || e.xIndex != index)
if (!set.isHighlightEnabled)
{
continue
}

let groupspace = barData.groupSpace
let isStack = h.stackIndex < 0 ? false : true

// calculate the correct x-position
let x = CGFloat(index * setCount + dataSetIndex) + groupspace / 2.0 + groupspace * CGFloat(index)
let barspaceHalf = set.barSpace / 2.0

let y1: Double
let y2: Double
let trans = dataProvider.getTransformer(set.axisDependency)

if (isStack)
{
y1 = h.range?.from ?? 0.0
y2 = h.range?.to ?? 0.0
}
else
{
y1 = e.value
y2 = 0.0
}

prepareBarHighlight(x: x, y1: y1, y2: y2, barspacehalf: barspaceHalf, trans: trans, rect: &barRect)
CGContextSetFillColorWithColor(context, set.highlightColor.CGColor)
CGContextSetAlpha(context, set.highlightAlpha)

CGContextFillRect(context, barRect)
let index = high.xIndex

if (drawHighlightArrowEnabled)
// check outofbounds
if (CGFloat(index) < (CGFloat(dataProvider.chartXMax) * animator.phaseX) / CGFloat(setCount))
{
CGContextSetAlpha(context, 1.0)

// distance between highlight arrow and bar
let offsetY = animator.phaseY * 0.07
let e = set.entryForXIndex(index) as! BarChartDataEntry!

CGContextSaveGState(context)

let pixelToValueMatrix = trans.pixelToValueMatrix
let xToYRel = abs(sqrt(pixelToValueMatrix.b * pixelToValueMatrix.b + pixelToValueMatrix.d * pixelToValueMatrix.d) / sqrt(pixelToValueMatrix.a * pixelToValueMatrix.a + pixelToValueMatrix.c * pixelToValueMatrix.c))
if (e === nil || e.xIndex != index)
{
continue
}

let arrowWidth = set.barSpace / 2.0
let arrowHeight = arrowWidth * xToYRel
let groupspace = barData.groupSpace
let isStack = high.stackIndex < 0 ? false : true

let yArrow = (y1 > -y2 ? y1 : y1) * Double(animator.phaseY)
// calculate the correct x-position
let x = CGFloat(index * setCount + dataSetIndex) + groupspace / 2.0 + groupspace * CGFloat(index)

_highlightArrowPtsBuffer[0].x = CGFloat(x) + 0.4
_highlightArrowPtsBuffer[0].y = CGFloat(yArrow) + offsetY
_highlightArrowPtsBuffer[1].x = CGFloat(x) + 0.4 + arrowWidth
_highlightArrowPtsBuffer[1].y = CGFloat(yArrow) + offsetY - arrowHeight
_highlightArrowPtsBuffer[2].x = CGFloat(x) + 0.4 + arrowWidth
_highlightArrowPtsBuffer[2].y = CGFloat(yArrow) + offsetY + arrowHeight
let y1: Double
let y2: Double

trans.pointValuesToPixel(&_highlightArrowPtsBuffer)
if (isStack)
{
y1 = high.range?.from ?? 0.0
y2 = high.range?.to ?? 0.0
}
else
{
y1 = e.value
y2 = 0.0
}

CGContextBeginPath(context)
CGContextMoveToPoint(context, _highlightArrowPtsBuffer[0].x, _highlightArrowPtsBuffer[0].y)
CGContextAddLineToPoint(context, _highlightArrowPtsBuffer[1].x, _highlightArrowPtsBuffer[1].y)
CGContextAddLineToPoint(context, _highlightArrowPtsBuffer[2].x, _highlightArrowPtsBuffer[2].y)
CGContextClosePath(context)
prepareBarHighlight(x: x, y1: y1, y2: y2, barspacehalf: barspaceHalf, trans: trans, rect: &barRect)

CGContextFillPath(context)
CGContextFillRect(context, barRect)

CGContextRestoreGState(context)
if (drawHighlightArrowEnabled)
{
CGContextSetAlpha(context, 1.0)

// distance between highlight arrow and bar
let offsetY = animator.phaseY * 0.07

CGContextSaveGState(context)

let pixelToValueMatrix = trans.pixelToValueMatrix
let xToYRel = abs(sqrt(pixelToValueMatrix.b * pixelToValueMatrix.b + pixelToValueMatrix.d * pixelToValueMatrix.d) / sqrt(pixelToValueMatrix.a * pixelToValueMatrix.a + pixelToValueMatrix.c * pixelToValueMatrix.c))

let arrowWidth = set.barSpace / 2.0
let arrowHeight = arrowWidth * xToYRel

let yArrow = (y1 > -y2 ? y1 : y1) * Double(animator.phaseY)

_highlightArrowPtsBuffer[0].x = CGFloat(x) + 0.4
_highlightArrowPtsBuffer[0].y = CGFloat(yArrow) + offsetY
_highlightArrowPtsBuffer[1].x = CGFloat(x) + 0.4 + arrowWidth
_highlightArrowPtsBuffer[1].y = CGFloat(yArrow) + offsetY - arrowHeight
_highlightArrowPtsBuffer[2].x = CGFloat(x) + 0.4 + arrowWidth
_highlightArrowPtsBuffer[2].y = CGFloat(yArrow) + offsetY + arrowHeight

trans.pointValuesToPixel(&_highlightArrowPtsBuffer)

CGContextBeginPath(context)
CGContextMoveToPoint(context, _highlightArrowPtsBuffer[0].x, _highlightArrowPtsBuffer[0].y)
CGContextAddLineToPoint(context, _highlightArrowPtsBuffer[1].x, _highlightArrowPtsBuffer[1].y)
CGContextAddLineToPoint(context, _highlightArrowPtsBuffer[2].x, _highlightArrowPtsBuffer[2].y)
CGContextClosePath(context)

CGContextFillPath(context)

CGContextRestoreGState(context)
}
}
}
}
Expand Down
163 changes: 87 additions & 76 deletions Charts/Classes/Renderers/BubbleChartRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -239,86 +239,97 @@ public class BubbleChartRenderer: ChartDataRendererBase
let phaseX = max(0.0, min(1.0, animator.phaseX))
let phaseY = animator.phaseY

for indice in indices
for high in indices
{
guard let dataSet = bubbleData.getDataSetByIndex(indice.dataSetIndex) as? IBubbleChartDataSet
where dataSet.isHighlightEnabled
else { continue }
let minDataSetIndex = high.dataSetIndex == -1 ? 0 : high.dataSetIndex
let maxDataSetIndex = high.dataSetIndex == -1 ? bubbleData.dataSetCount : (high.dataSetIndex + 1)
if maxDataSetIndex - minDataSetIndex < 1 { continue }

guard let
entry = bubbleData.getEntryForHighlight(indice) as? BubbleChartDataEntry
where entry.xIndex == indice.xIndex
else { continue }

let entryFrom = dataSet.entryForXIndex(self.minX)
let entryTo = dataSet.entryForXIndex(self.maxX)

let minx = max(dataSet.entryIndex(entry: entryFrom!), 0)
let maxx = min(dataSet.entryIndex(entry: entryTo!) + 1, dataSet.entryCount)

let trans = dataProvider.getTransformer(dataSet.axisDependency)

_sizeBuffer[0].x = 0.0
_sizeBuffer[0].y = 0.0
_sizeBuffer[1].x = 1.0
_sizeBuffer[1].y = 0.0

trans.pointValuesToPixel(&_sizeBuffer)

let normalizeSize = dataSet.isNormalizeSizeEnabled

// calcualte the full width of 1 step on the x-axis
let maxBubbleWidth: CGFloat = abs(_sizeBuffer[1].x - _sizeBuffer[0].x)
let maxBubbleHeight: CGFloat = abs(viewPortHandler.contentBottom - viewPortHandler.contentTop)
let referenceSize: CGFloat = min(maxBubbleHeight, maxBubbleWidth)

_pointBuffer.x = CGFloat(entry.xIndex - minx) * phaseX + CGFloat(minx)
_pointBuffer.y = CGFloat(entry.value) * phaseY
trans.pointValueToPixel(&_pointBuffer)

let shapeSize = getShapeSize(entrySize: entry.size, maxSize: dataSet.maxSize, reference: referenceSize, normalizeSize: normalizeSize)
let shapeHalf = shapeSize / 2.0

if (!viewPortHandler.isInBoundsTop(_pointBuffer.y + shapeHalf)
|| !viewPortHandler.isInBoundsBottom(_pointBuffer.y - shapeHalf))
{
continue
}

if (!viewPortHandler.isInBoundsLeft(_pointBuffer.x + shapeHalf))
{
continue
}

if (!viewPortHandler.isInBoundsRight(_pointBuffer.x - shapeHalf))
for dataSetIndex in minDataSetIndex..<maxDataSetIndex
{
break
}

if (indice.xIndex < minx || indice.xIndex >= maxx)
{
continue
guard let dataSet = bubbleData.getDataSetByIndex(dataSetIndex) as? IBubbleChartDataSet
where dataSet.isHighlightEnabled
else { continue }

let entries = dataSet.entriesForXIndex(high.xIndex)

for entry in entries
{
guard let entry = entry as? BubbleChartDataEntry
else { continue }
if !isnan(high.value) && entry.value != high.value { continue }

let entryFrom = dataSet.entryForXIndex(self.minX)
let entryTo = dataSet.entryForXIndex(self.maxX)

let minx = max(dataSet.entryIndex(entry: entryFrom!), 0)
let maxx = min(dataSet.entryIndex(entry: entryTo!) + 1, dataSet.entryCount)

let trans = dataProvider.getTransformer(dataSet.axisDependency)

_sizeBuffer[0].x = 0.0
_sizeBuffer[0].y = 0.0
_sizeBuffer[1].x = 1.0
_sizeBuffer[1].y = 0.0

trans.pointValuesToPixel(&_sizeBuffer)

let normalizeSize = dataSet.isNormalizeSizeEnabled

// calcualte the full width of 1 step on the x-axis
let maxBubbleWidth: CGFloat = abs(_sizeBuffer[1].x - _sizeBuffer[0].x)
let maxBubbleHeight: CGFloat = abs(viewPortHandler.contentBottom - viewPortHandler.contentTop)
let referenceSize: CGFloat = min(maxBubbleHeight, maxBubbleWidth)

_pointBuffer.x = CGFloat(entry.xIndex - minx) * phaseX + CGFloat(minx)
_pointBuffer.y = CGFloat(entry.value) * phaseY
trans.pointValueToPixel(&_pointBuffer)

let shapeSize = getShapeSize(entrySize: entry.size, maxSize: dataSet.maxSize, reference: referenceSize, normalizeSize: normalizeSize)
let shapeHalf = shapeSize / 2.0

if (!viewPortHandler.isInBoundsTop(_pointBuffer.y + shapeHalf)
|| !viewPortHandler.isInBoundsBottom(_pointBuffer.y - shapeHalf))
{
continue
}

if (!viewPortHandler.isInBoundsLeft(_pointBuffer.x + shapeHalf))
{
continue
}

if (!viewPortHandler.isInBoundsRight(_pointBuffer.x - shapeHalf))
{
break
}

if (high.xIndex < minx || high.xIndex >= maxx)
{
continue
}

let originalColor = dataSet.colorAt(entry.xIndex)

var h: CGFloat = 0.0
var s: CGFloat = 0.0
var b: CGFloat = 0.0
var a: CGFloat = 0.0

originalColor.getHue(&h, saturation: &s, brightness: &b, alpha: &a)

let color = NSUIColor(hue: h, saturation: s, brightness: b * 0.5, alpha: a)
let rect = CGRect(
x: _pointBuffer.x - shapeHalf,
y: _pointBuffer.y - shapeHalf,
width: shapeSize,
height: shapeSize)

CGContextSetLineWidth(context, dataSet.highlightCircleWidth)
CGContextSetStrokeColorWithColor(context, color.CGColor)
CGContextStrokeEllipseInRect(context, rect)
}
}

let originalColor = dataSet.colorAt(entry.xIndex)

var h: CGFloat = 0.0
var s: CGFloat = 0.0
var b: CGFloat = 0.0
var a: CGFloat = 0.0

originalColor.getHue(&h, saturation: &s, brightness: &b, alpha: &a)

let color = NSUIColor(hue: h, saturation: s, brightness: b * 0.5, alpha: a)
let rect = CGRect(
x: _pointBuffer.x - shapeHalf,
y: _pointBuffer.y - shapeHalf,
width: shapeSize,
height: shapeSize)

CGContextSetLineWidth(context, dataSet.highlightCircleWidth)
CGContextSetStrokeColorWithColor(context, color.CGColor)
CGContextStrokeEllipseInRect(context, rect)
}

CGContextRestoreGState(context)
Expand Down
Loading

0 comments on commit b08ec80

Please sign in to comment.