-
-
Save alfredreynold/d961e386f84bda305b750b5a32fc983e to your computer and use it in GitHub Desktop.
User Interaction at runtime with MPAndroidChart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
Following are classes I subclassed, | |
This is a rough work. | |
When you assign a data value to DCCombinedChartView, it creates necessary UIViews which has pan gesture recognizer attached to it, | |
When that UIView detects PAN then it reposition itself and changes the data associated to that data point | |
**/ | |
public class DCCombinedChartView: CombinedChartView { | |
var viewsForData:[ChartDataEntry:DCChartDataPointerView]? = [:] | |
var viewsForDataByXIndex:[Int:DCChartDataPointerView] = [:] | |
var isViewsCreated = false | |
var dcLineRend:DCLineChartRenderer! | |
var timeFormattor:NSDateFormatter = NSDateFormatter() | |
let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)! | |
public override func initialize() | |
{ | |
super.initialize() | |
self.dcLineRend = DCLineChartRenderer(dataProvider: self, animator: self.chartAnimator, viewPortHandler: self.viewPortHandler) | |
let combinedRend = self.renderer as! CombinedChartRenderer | |
var newRend = [ChartDataRendererBase]() | |
for r in combinedRend.subRenderers { | |
if r.isKindOfClass(LineChartRenderer) { | |
newRend.append(self.dcLineRend) | |
} else { | |
newRend.append(r) | |
} | |
} | |
combinedRend.subRenderers = newRend | |
timeFormattor.dateFormat = "HH.mm" | |
} | |
public override var data: ChartData? | |
{ | |
get | |
{ | |
return super.data | |
} | |
set | |
{ | |
super.data = newValue | |
if !isViewsCreated { | |
let combinedRend = self.renderer as! CombinedChartRenderer | |
var newRend = [ChartDataRendererBase]() | |
for r in combinedRend.subRenderers { | |
if r.isKindOfClass(LineChartRenderer) { | |
newRend.append(self.dcLineRend) | |
} else { | |
newRend.append(r) | |
} | |
} | |
combinedRend.subRenderers = newRend | |
for d in (super.data as! CombinedChartData).lineData.dataSets { | |
for i in 0 ..< d.entryCount { | |
let ent = d.entryForIndex(i) | |
let xIndex = ent?.xIndex | |
var v:DCChartDataPointerView! | |
if let o = viewsForDataByXIndex[xIndex!] { | |
v = o | |
} else { | |
v = DCChartDataPointerView(frame: CGRect(x: 0, y: 0, width: 14, height: 14)) | |
viewsForDataByXIndex[xIndex!] = v | |
} | |
v.data_ChartEntry.append(ent!) | |
for ld in (super.data as! CombinedChartData).bubbleData.dataSets { | |
for k in 0 ..< ld.entryCount { | |
let dataEnt = ld.entryForIndex(k) | |
if xIndex == dataEnt?.xIndex { | |
v.data_ChartEntry.append(dataEnt!) | |
v.bubbleDataSetForTime = ld | |
v.intialXIndex = xIndex! | |
} | |
} | |
} | |
v.whenViewPans = {(newPoint,bds) in | |
let point : CGPoint = self.getValueByTouchPoint(pt: newPoint, axis: ChartYAxis.AxisDependency.Left) | |
// let ct = ChartTransformer(viewPortHandler: self.viewPortHandler) | |
// ct.pixelToValue(&point) | |
if let bds = bds { | |
let label = bds.valueFormatter?.zeroSymbol | |
let entryData = bds.entryForIndex(0) | |
let startDate = entryData?.data as! NSDate | |
if ((label?.containsString(".")) != nil) { | |
let diff = -(v.intialXIndex - Int(point.x)) | |
if diff != v.lastDiff { | |
v.lastDiff = diff | |
let newdate = self.calendar.dateByAddingUnit(.Minute, value: Int(point.x), toDate: startDate, options: []) | |
let newLabel = self.timeFormattor.stringFromDate(newdate!) | |
bds.valueFormatter?.zeroSymbol = newLabel | |
for dcent in v.data_ChartEntry { | |
dcent.xIndex = Int(point.x) | |
} | |
} | |
} | |
} | |
// if let dd:IBarLineScatterCandleBubbleChartDataSet = self.getDataSetByTouchPoint(newPoint){ | |
// print("Alfa") | |
// print(dd.entryForIndex(0)) | |
// } | |
// for ld in (super.data as! CombinedChartData).bubbleData.dataSets { | |
// for k in 0 ..< ld.entryCount { | |
// let dataEnt = ld.entryForIndex(k) | |
// dataEnt?.xIndex = Int(point.x) | |
// } | |
// } | |
self.setNeedsDisplay() | |
} | |
self.addSubview(v) | |
v.backgroundColor = UIColor.clearColor() | |
v.userInteractionEnabled = true | |
viewsForData![ent!] = v | |
isViewsCreated = true | |
} | |
} | |
} | |
dcLineRend.viewsForData = self.viewsForData! | |
} | |
} | |
/* | |
// Only override drawRect: if you perform custom drawing. | |
// An empty implementation adversely affects performance during animation. | |
override func drawRect(rect: CGRect) { | |
// Drawing code | |
} | |
*/ | |
} | |
class DCChartDataPointerView: UIView, UIGestureRecognizerDelegate { | |
var panGest:UIPanGestureRecognizer! | |
var data_ChartEntry:[ChartDataEntry] = [] | |
var whenViewPans:(newPoint:CGPoint, bds:IChartDataSet?)->() = {(pt,bds) in} | |
var isPanning = false | |
var bubbleDataSetForTime:IChartDataSet? | |
var intialXIndex:Int = 0 | |
var lastDiff:Int = 0 | |
override func didMoveToSuperview() { | |
if panGest == nil { | |
panGest = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:))) | |
panGest.delegate = self | |
self.addGestureRecognizer(panGest) | |
} | |
} | |
func handlePan(recognizer:UIPanGestureRecognizer) { | |
if recognizer.state == .Began { | |
isPanning = true | |
} else if recognizer.state == UIGestureRecognizerState.Changed { | |
let translation = recognizer.translationInView(self) | |
self.center.x += CGFloat(Int(translation.x)) | |
recognizer.setTranslation(CGPointZero, inView: self) | |
dispatch_async(dispatch_get_main_queue(), { | |
self.whenViewPans(newPoint: self.center,bds: self.bubbleDataSetForTime) | |
}) | |
} else if recognizer.state == .Ended { | |
isPanning = false | |
} | |
} | |
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { | |
return false | |
} | |
} | |
public class DCLineChartRenderer:LineChartRenderer { | |
public var viewsForData:[ChartDataEntry:UIView]? = [:] | |
public override init(dataProvider: LineChartDataProvider?, animator: ChartAnimator?, viewPortHandler: ChartViewPortHandler) | |
{ | |
super.init(dataProvider: dataProvider, animator: animator, viewPortHandler: viewPortHandler) | |
} | |
private var _lineSegments = [CGPoint](count: 2, repeatedValue: CGPoint()) | |
public override func drawLinear(context context: CGContext, dataSet: ILineChartDataSet) | |
{ | |
guard let | |
trans = dataProvider?.getTransformer(dataSet.axisDependency), | |
animator = animator | |
else { return } | |
let valueToPixelMatrix = trans.valueToPixelMatrix | |
let entryCount = dataSet.entryCount | |
let isDrawSteppedEnabled = dataSet.mode == .Stepped | |
let pointsPerEntryPair = isDrawSteppedEnabled ? 4 : 2 | |
let phaseX = max(0.0, min(1.0, animator.phaseX)) | |
let phaseY = animator.phaseY | |
guard let | |
entryFrom = dataSet.entryForXIndex(self.minX < 0 ? 0 : self.minX, rounding: .Down), | |
entryTo = dataSet.entryForXIndex(self.maxX, rounding: .Up) | |
else { return } | |
var diff = (entryFrom == entryTo) ? 1 : 0 | |
if dataSet.mode == .CubicBezier | |
{ | |
diff += 1 | |
} | |
let minx = max(dataSet.entryIndex(entry: entryFrom) - diff, 0) | |
let maxx = min(max(minx + 2, dataSet.entryIndex(entry: entryTo) + 1), entryCount) | |
CGContextSaveGState(context) | |
CGContextSetLineCap(context, dataSet.lineCapType) | |
// more than 1 color | |
if (dataSet.colors.count > 1) | |
{ | |
if (_lineSegments.count != pointsPerEntryPair) | |
{ | |
_lineSegments = [CGPoint](count: pointsPerEntryPair, repeatedValue: CGPoint()) | |
} | |
let count = Int(ceil(CGFloat(maxx - minx) * phaseX + CGFloat(minx))) | |
for j in minx.stride(to: count, by: 1) | |
{ | |
if (count > 1 && j == count - 1) | |
{ // Last point, we have already drawn a line to this point | |
break | |
} | |
var e: ChartDataEntry! = dataSet.entryForIndex(j) | |
if e == nil { continue } | |
_lineSegments[0].x = CGFloat(e.xIndex) | |
_lineSegments[0].y = CGFloat(e.value) * phaseY | |
if let v = self.viewsForData![e] as? DCChartDataPointerView { | |
if !v.isPanning { | |
v.frame.origin.x = _lineSegments[0].x - 7 | |
v.frame.origin.y = _lineSegments[0].y - 7 | |
} | |
v.hidden = false | |
v.superview?.bringSubviewToFront(v) | |
} | |
if (j + 1 < count) | |
{ | |
e = dataSet.entryForIndex(j + 1) | |
if e == nil { break } | |
if isDrawSteppedEnabled | |
{ | |
_lineSegments[1] = CGPoint(x: CGFloat(e.xIndex), y: _lineSegments[0].y) | |
_lineSegments[2] = _lineSegments[1] | |
_lineSegments[3] = CGPoint(x: CGFloat(e.xIndex), y: CGFloat(e.value) * phaseY) | |
} | |
else | |
{ | |
_lineSegments[1] = CGPoint(x: CGFloat(e.xIndex), y: CGFloat(e.value) * phaseY) | |
} | |
} | |
else | |
{ | |
_lineSegments[1] = _lineSegments[0] | |
} | |
for i in 0..<_lineSegments.count | |
{ | |
_lineSegments[i] = CGPointApplyAffineTransform(_lineSegments[i], valueToPixelMatrix) | |
} | |
if (!viewPortHandler.isInBoundsRight(_lineSegments[0].x)) | |
{ | |
break | |
} | |
// make sure the lines don't do shitty things outside bounds | |
if (!viewPortHandler.isInBoundsLeft(_lineSegments[1].x) | |
|| (!viewPortHandler.isInBoundsTop(_lineSegments[0].y) && !viewPortHandler.isInBoundsBottom(_lineSegments[1].y)) | |
|| (!viewPortHandler.isInBoundsTop(_lineSegments[0].y) && !viewPortHandler.isInBoundsBottom(_lineSegments[1].y))) | |
{ | |
continue | |
} | |
// get the color that is set for this line-segment | |
CGContextSetStrokeColorWithColor(context, dataSet.colorAt(j).CGColor) | |
CGContextStrokeLineSegments(context, _lineSegments, pointsPerEntryPair) | |
} | |
} | |
else | |
{ // only one color per dataset | |
var e1: ChartDataEntry! | |
var e2: ChartDataEntry! | |
if (_lineSegments.count != max((entryCount - 1) * pointsPerEntryPair, pointsPerEntryPair)) | |
{ | |
_lineSegments = [CGPoint](count: max((entryCount - 1) * pointsPerEntryPair, pointsPerEntryPair), repeatedValue: CGPoint()) | |
} | |
e1 = dataSet.entryForIndex(minx) | |
if e1 != nil | |
{ | |
let count = Int(ceil(CGFloat(maxx - minx) * phaseX + CGFloat(minx))) | |
var j = 0 | |
for x in (count > 1 ? minx + 1 : minx).stride(to: count, by: 1) | |
{ | |
e1 = dataSet.entryForIndex(x == 0 ? 0 : (x - 1)) | |
e2 = dataSet.entryForIndex(x) | |
if e1 == nil || e2 == nil { continue } | |
_lineSegments[j] = CGPointApplyAffineTransform( | |
CGPoint( | |
x: CGFloat(e1.xIndex), | |
y: CGFloat(e1.value) * phaseY | |
), valueToPixelMatrix) | |
if let v = self.viewsForData![e1] as? DCChartDataPointerView { | |
if !v.isPanning { | |
v.frame.origin.x = _lineSegments[j].x - 7 | |
v.frame.origin.y = _lineSegments[j].y - 7 | |
} | |
v.hidden = false | |
v.superview?.bringSubviewToFront(v) | |
} | |
j += 1 | |
if isDrawSteppedEnabled | |
{ | |
_lineSegments[j] = CGPointApplyAffineTransform( | |
CGPoint( | |
x: CGFloat(e2.xIndex), | |
y: CGFloat(e1.value) * phaseY | |
), valueToPixelMatrix) | |
j += 1 | |
_lineSegments[j] = CGPointApplyAffineTransform( | |
CGPoint( | |
x: CGFloat(e2.xIndex), | |
y: CGFloat(e1.value) * phaseY | |
), valueToPixelMatrix) | |
j += 1 | |
} | |
_lineSegments[j] = CGPointApplyAffineTransform( | |
CGPoint( | |
x: CGFloat(e2.xIndex), | |
y: CGFloat(e2.value) * phaseY | |
), valueToPixelMatrix) | |
if let v = self.viewsForData![e2] as? DCChartDataPointerView { | |
if !v.isPanning { | |
v.frame.origin.x = _lineSegments[j].x - 7 | |
v.frame.origin.y = _lineSegments[j].y - 7 | |
} | |
v.hidden = false | |
v.superview?.bringSubviewToFront(v) | |
} | |
j += 1 | |
} | |
if j > 0 | |
{ | |
let size = max((count - minx - 1) * pointsPerEntryPair, pointsPerEntryPair) | |
CGContextSetStrokeColorWithColor(context, dataSet.colorAt(0).CGColor) | |
CGContextStrokeLineSegments(context, _lineSegments, size) | |
} | |
} | |
} | |
CGContextRestoreGState(context) | |
// if drawing filled is enabled | |
if (dataSet.isDrawFilledEnabled && entryCount > 0) | |
{ | |
drawLinearFill(context: context, dataSet: dataSet, minx: minx, maxx: maxx, trans: trans) | |
} | |
} | |
} | |
public class DCBubbleChartRenderer: BubbleChartRenderer { | |
public var viewsForData:[ChartDataEntry:UIView]? = [:] | |
public override init(dataProvider: BubbleChartDataProvider?, animator: ChartAnimator?, viewPortHandler: ChartViewPortHandler) | |
{ | |
super.init(dataProvider:dataProvider, animator: animator, viewPortHandler: viewPortHandler) | |
} | |
private var _pointBuffer = CGPoint() | |
private var _sizeBuffer = [CGPoint](count: 2, repeatedValue: CGPoint()) | |
private func getShapeSize( | |
entrySize entrySize: CGFloat, | |
maxSize: CGFloat, | |
reference: CGFloat, | |
normalizeSize: Bool) -> CGFloat | |
{ | |
let factor: CGFloat = normalizeSize | |
? ((maxSize == 0.0) ? 1.0 : sqrt(entrySize / maxSize)) | |
: entrySize | |
let shapeSize: CGFloat = reference * factor | |
return shapeSize | |
} | |
public override func drawData(context context: CGContext) | |
{ | |
guard let dataProvider = dataProvider, bubbleData = dataProvider.bubbleData else { return } | |
for (_,v) in viewsForData! {v.hidden = true} | |
for set in bubbleData.dataSets as! [IBubbleChartDataSet] | |
{ | |
if set.isVisible && set.entryCount > 0 | |
{ | |
drawDataSet(context: context, dataSet: set) | |
} | |
} | |
} | |
public override func drawDataSet(context context: CGContext, dataSet: IBubbleChartDataSet) | |
{ | |
// super.drawDataSet(context: context, dataSet: dataSet) | |
guard let | |
dataProvider = dataProvider, | |
animator = animator | |
else { return } | |
let trans = dataProvider.getTransformer(dataSet.axisDependency) | |
let phaseX = max(0.0, min(1.0, animator.phaseX)) | |
let phaseY = animator.phaseY | |
let entryCount = dataSet.entryCount | |
let valueToPixelMatrix = trans.valueToPixelMatrix | |
guard let | |
entryFrom = dataSet.entryForXIndex(self.minX), | |
entryTo = dataSet.entryForXIndex(self.maxX) | |
else { return } | |
let minx = max(dataSet.entryIndex(entry: entryFrom), 0) | |
let maxx = min(dataSet.entryIndex(entry: entryTo) + 1, entryCount) | |
_sizeBuffer[0].x = 0.0 | |
_sizeBuffer[0].y = 0.0 | |
_sizeBuffer[1].x = 1.0 | |
_sizeBuffer[1].y = 0.0 | |
trans.pointValuesToPixel(&_sizeBuffer) | |
CGContextSaveGState(context) | |
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) | |
for j in minx ..< maxx | |
{ | |
guard let entry = dataSet.entryForIndex(j) as? BubbleChartDataEntry else { continue } | |
_pointBuffer.x = CGFloat(entry.xIndex - minx) * phaseX + CGFloat(minx) | |
_pointBuffer.y = CGFloat(entry.value) * phaseY | |
_pointBuffer = CGPointApplyAffineTransform(_pointBuffer, valueToPixelMatrix) | |
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 | |
} | |
let color = dataSet.colorAt(entry.xIndex) | |
let rect = CGRect( | |
x: _pointBuffer.x - shapeHalf, | |
y: _pointBuffer.y - shapeHalf, | |
width: shapeSize, | |
height: shapeSize | |
) | |
if let v = self.viewsForData![entry] as? DCChartDataPointerView { | |
if !v.isPanning { | |
v.frame.origin.x = _pointBuffer.x - shapeHalf | |
v.frame.origin.y = _pointBuffer.y - shapeHalf | |
} | |
v.hidden = false | |
v.superview?.bringSubviewToFront(v) | |
} | |
CGContextSetFillColorWithColor(context, color.CGColor) | |
CGContextFillEllipseInRect(context, rect) | |
} | |
CGContextRestoreGState(context) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment