Skip to content

Instantly share code, notes, and snippets.

@alfredreynold
Created January 2, 2017 13:09
Show Gist options
  • Save alfredreynold/d961e386f84bda305b750b5a32fc983e to your computer and use it in GitHub Desktop.
Save alfredreynold/d961e386f84bda305b750b5a32fc983e to your computer and use it in GitHub Desktop.
User Interaction at runtime with MPAndroidChart
/**
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