Last active
December 26, 2016 22:16
-
-
Save ivnsch/badf5ac8d8de3bb748f2 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// | |
// ScatterExample.swift | |
// Examples | |
// | |
// Created by ischuetz on 16/05/15. | |
// Copyright (c) 2015 ivanschuetz. All rights reserved. | |
// | |
import UIKit | |
import CoreGraphics | |
class BubbleExample: UIViewController { | |
private var chart: Chart? | |
private let colorBarHeight: CGFloat = 50 | |
private let useViewsLayer = true | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
let frame = ExamplesDefaults.chartFrame(self.view.bounds) | |
let chartFrame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height - colorBarHeight) | |
let colorBar = ColorBar(frame: CGRectMake(0, chartFrame.origin.y + chartFrame.size.height, self.view.frame.size.width, self.colorBarHeight), c1: UIColor.redColor(), c2: UIColor.greenColor()) | |
self.view.addSubview(colorBar) | |
let labelSettings = ChartLabelSettings(font: ExamplesDefaults.labelFont) | |
func toColor(percentage: CGFloat) -> UIColor { | |
return colorBar.colorForPercentage(percentage).colorWithAlphaComponent(0.6) | |
} | |
let chartPoints: [ChartPointBubble] = [ | |
(2, 2, 100, toColor(0)), | |
(2.1, 5, 250, toColor(0)), | |
(4, 4, 200, toColor(0.2)), | |
(2.3, 5, 150, toColor(0.7)), | |
(6, 7, 120, toColor(0.9)), | |
(8, 3, 50, toColor(1)), | |
(2, 4.5, 80, toColor(0.7)), | |
(2, 5.2, 50, toColor(0.4)), | |
(2, 4, 100, toColor(0.3)), | |
(2.7, 5.5, 200, toColor(0.5)), | |
(1.7, 2.8, 150, toColor(0.7)), | |
(4.4, 8, 120, toColor(0.9)), | |
(5, 6.3, 250, toColor(1)), | |
(6, 8, 100, toColor(0)), | |
(4, 8.5, 200, toColor(0.5)), | |
(8, 5, 200, toColor(0.6)), | |
(8.5, 10, 150, toColor(0.7)), | |
(9, 11, 120, toColor(0.6)), | |
(10, 6, 100, toColor(1)), | |
(11, 7, 100, toColor(0)), | |
(11, 4, 200, toColor(0.5)), | |
(11.5, 10, 150, toColor(0.7)), | |
(12, 7, 120, toColor(0.9)), | |
(12, 9, 250, toColor(0.8)) | |
].map{ChartPointBubble(x: ChartAxisValueFloat(CGFloat($0), labelSettings: labelSettings), y: ChartAxisValueFloat(CGFloat($1)), diameterScalar: $2, bgColor: $3)} | |
let xValues = Array(stride(from: -2, through: 14, by: 2)).map {ChartAxisValueInt($0, labelSettings: labelSettings)} | |
let yValues = Array(stride(from: -2, through: 12, by: 2)).map {ChartAxisValueInt($0, labelSettings: labelSettings)} | |
let xModel = ChartAxisModel(axisValues: xValues, axisTitleLabel: ChartAxisLabel(text: "Axis title", settings: labelSettings)) | |
let yModel = ChartAxisModel(axisValues: yValues, axisTitleLabel: ChartAxisLabel(text: "Axis title", settings: labelSettings)) | |
let coordsSpace = ChartCoordsSpaceLeftBottomSingleAxis(chartSettings: ExamplesDefaults.chartSettings, chartFrame: chartFrame, xModel: xModel, yModel: yModel) | |
let (xAxis, yAxis, innerFrame) = (coordsSpace.xAxis, coordsSpace.yAxis, coordsSpace.chartInnerFrame) | |
let lineModel = ChartLineModel(chartPoints: chartPoints, lineColor: UIColor.redColor(), animDuration: 0.5, animDelay: 0) | |
let bubbleLayer = self.bubblesLayer(xAxis: xAxis, yAxis: yAxis, chartInnerFrame: innerFrame, chartPoints: chartPoints) | |
let guidelinesLayerSettings = ChartGuideLinesDottedLayerSettings(linesColor: UIColor.blackColor(), linesWidth: ExamplesDefaults.guidelinesWidth) | |
let guidelinesLayer = ChartGuideLinesDottedLayer(xAxis: xAxis, yAxis: yAxis, innerFrame: innerFrame, settings: guidelinesLayerSettings) | |
let guidelinesHighlightLayerSettings = ChartGuideLinesDottedLayerSettings(linesColor: UIColor.redColor(), linesWidth: 1, dotWidth: 4, dotSpacing: 4) | |
let guidelinesHighlightLayer = ChartGuideLinesForValuesDottedLayer(xAxis: xAxis, yAxis: yAxis, innerFrame: innerFrame, settings: guidelinesHighlightLayerSettings, axisValuesX: [ChartAxisValueFloat(0)], axisValuesY: [ChartAxisValueFloat(0)]) | |
let chart = Chart( | |
frame: chartFrame, | |
layers: [ | |
xAxis, | |
yAxis, | |
guidelinesLayer, | |
guidelinesHighlightLayer, | |
bubbleLayer | |
] | |
) | |
self.view.addSubview(chart.view) | |
self.chart = chart | |
} | |
// We can use a view based layer for easy animation (or interactivity), in which case we use the default chart points layer with a generator to create bubble views. | |
// On the other side, if we don't need animation or want a better performance, we use ChartPointsBubbleLayer, which instead of creating views, renders directly to the chart's context. | |
private func bubblesLayer(#xAxis: ChartAxisLayer, yAxis: ChartAxisLayer, chartInnerFrame: CGRect, chartPoints: [ChartPointBubble]) -> ChartLayer { | |
let maxBubbleDiameter: CGFloat = 30, minBubbleDiameter: CGFloat = 2 | |
if self.useViewsLayer == true { | |
let (minDiameterScalar: CGFloat, maxDiameterScalar: CGFloat) = chartPoints.reduce((min: CGFloat(0), max: CGFloat(0))) {tuple, chartPoint in | |
(min: min(tuple.min, chartPoint.diameterScalar), max: max(tuple.max, chartPoint.diameterScalar)) | |
} | |
let diameterFactor = (maxBubbleDiameter - minBubbleDiameter) / (maxDiameterScalar - minDiameterScalar) | |
var popups: [UIView] = [] | |
return ChartPointsViewsLayer(xAxis: xAxis, yAxis: yAxis, innerFrame: chartInnerFrame, chartPoints: chartPoints, viewGenerator: {(chartPointModel, layer, chart) -> UIView? in | |
let (chartPoint, screenLoc) = (chartPointModel.chartPoint, chartPointModel.screenLoc) | |
let diameter = chartPointModel.chartPoint.diameterScalar * diameterFactor | |
let rect = CGRectMake(chartPointModel.screenLoc.x - diameter / 2, chartPointModel.screenLoc.y - diameter / 2, diameter, diameter) | |
let bubbleView = MyBubbleView(frame: rect, fillColor: chartPointModel.chartPoint.bgColor, borderColor: UIColor.blackColor().colorWithAlphaComponent(0.6), animDelay: Float(chartPointModel.index) * 0.2, animDuration: 1.2) | |
bubbleView.touchHandler = {view in | |
for p in popups {p.removeFromSuperview()} | |
let w: CGFloat = Env.iPad ? 250 : 150 | |
let h: CGFloat = Env.iPad ? 100 : 80 | |
let x: CGFloat = { | |
let attempt = screenLoc.x - (w/2) | |
let leftBound: CGFloat = chart.bounds.origin.x | |
let rightBound = chart.bounds.size.width - 5 | |
if attempt < leftBound { | |
return view.frame.origin.x | |
} else if attempt + w > rightBound { | |
return rightBound - w | |
} | |
return attempt | |
}() | |
let frame = CGRectMake(x, screenLoc.y - (h + (Env.iPad ? 30 : 12)), w, h) | |
let bubbleView = BubbleView(frame: frame, arrowWidth: Env.iPad ? 40 : 28, arrowHeight: Env.iPad ? 20 : 14, bgColor: UIColor.blackColor(), arrowX: screenLoc.x - x) | |
chart.addSubview(bubbleView) | |
bubbleView.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(0, 0), CGAffineTransformMakeTranslation(0, 100)) | |
let infoView = UILabel(frame: CGRectMake(0, 10, w, h - 30)) | |
infoView.textColor = UIColor.whiteColor() | |
infoView.backgroundColor = UIColor.blackColor() | |
infoView.text = "Some text about \(chartPoint.text)" | |
infoView.font = ExamplesDefaults.fontWithSize(Env.iPad ? 14 : 12) | |
infoView.textAlignment = NSTextAlignment.Center | |
bubbleView.addSubview(infoView) | |
popups.append(bubbleView) | |
UIView.animateWithDuration(0.2, delay: 0, options: UIViewAnimationOptions.allZeros, animations: { | |
bubbleView.transform = CGAffineTransformIdentity | |
}, completion: {finished in}) | |
} | |
return bubbleView | |
}) | |
} else { | |
return ChartPointsBubbleLayer(xAxis: xAxis, yAxis: yAxis, innerFrame: chartInnerFrame, chartPoints: chartPoints) | |
} | |
} | |
class MyBubbleView: UIView { | |
let fillColor: UIColor | |
let borderColor: UIColor | |
let borderWidth: CGFloat | |
let animDelay: Float | |
let animDuration: Float | |
var touchHandler: ((MyBubbleView) -> ())? | |
init(frame: CGRect, fillColor: UIColor, borderColor: UIColor, borderWidth: CGFloat = 1, animDelay: Float, animDuration: Float) { | |
self.fillColor = fillColor | |
self.borderColor = borderColor | |
self.borderWidth = borderWidth | |
self.animDelay = animDelay | |
self.animDuration = animDuration | |
super.init(frame: CGRectInset(frame, -borderWidth, -borderWidth)) | |
self.backgroundColor = UIColor.clearColor() | |
} | |
required init(coder aDecoder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
override func drawRect(rect: CGRect) { | |
let context = UIGraphicsGetCurrentContext() | |
CGContextSetLineWidth(context, self.borderWidth) | |
CGContextSetStrokeColorWithColor(context, self.borderColor.CGColor) | |
CGContextSetFillColorWithColor(context, self.fillColor.CGColor) | |
let circleRect = (CGRectMake(self.borderWidth, self.borderWidth, self.frame.size.width - (self.borderWidth * 2), self.frame.size.height - (self.borderWidth * 2))) | |
CGContextFillEllipseInRect(context, circleRect) | |
CGContextStrokeEllipseInRect(context, circleRect) | |
} | |
override func didMoveToSuperview() { | |
self.transform = CGAffineTransformMakeScale(0.1, 0.1) | |
self.alpha = 0 | |
UIView.animateWithDuration(NSTimeInterval(self.animDuration), delay: NSTimeInterval(self.animDelay), usingSpringWithDamping: 0.4, initialSpringVelocity: 0.5, options: UIViewAnimationOptions.allZeros, animations: { () -> Void in | |
self.transform = CGAffineTransformMakeScale(1, 1) | |
self.alpha = 1 | |
}, completion: nil) | |
} | |
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) { | |
self.touchHandler?(self) | |
} | |
} | |
class ColorBar: UIView { | |
let dividers: [CGFloat] | |
let gradientImg: UIImage | |
lazy var imgData: UnsafePointer<UInt8> = { | |
let provider = CGImageGetDataProvider(self.gradientImg.CGImage) | |
let pixelData = CGDataProviderCopyData(provider) | |
return CFDataGetBytePtr(pixelData) | |
}() | |
init(frame: CGRect, c1: UIColor, c2: UIColor) { | |
var gradient: CAGradientLayer = CAGradientLayer() | |
gradient.frame = CGRectMake(0, 0, frame.width, 30) | |
gradient.colors = [UIColor.blueColor().CGColor, UIColor.cyanColor().CGColor, UIColor.yellowColor().CGColor, UIColor.redColor().CGColor] | |
gradient.startPoint = CGPointMake(0, 0.5) | |
gradient.endPoint = CGPointMake(1.0, 0.5) | |
let imgHeight = 1 | |
let imgWidth = Int(gradient.bounds.size.width) | |
let bitmapBytesPerRow = imgWidth * 4 | |
let bitmapByteCount = bitmapBytesPerRow * imgHeight | |
let colorSpace:CGColorSpace = CGColorSpaceCreateDeviceRGB() | |
let bitmapInfo = CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue) | |
let context = CGBitmapContextCreate (nil, | |
imgWidth, | |
imgHeight, | |
8, | |
bitmapBytesPerRow, | |
colorSpace, | |
bitmapInfo) | |
UIGraphicsBeginImageContext(gradient.bounds.size) | |
gradient.renderInContext(context) | |
let gradientImg = UIImage(CGImage: CGBitmapContextCreateImage(context))! | |
UIGraphicsEndImageContext() | |
self.gradientImg = gradientImg | |
let segmentSize = gradient.frame.size.width / 6 | |
self.dividers = Array(stride(from: segmentSize, through: gradient.frame.size.width, by: segmentSize)) | |
super.init(frame: frame) | |
self.layer.insertSublayer(gradient, atIndex: 0) | |
let numberFormatter = NSNumberFormatter() | |
numberFormatter.maximumFractionDigits = 2 | |
for x in stride(from: segmentSize, through: gradient.frame.size.width - 1, by: segmentSize) { | |
let dividerW: CGFloat = 1 | |
let divider = UIView(frame: CGRectMake(x - dividerW / 2, 25, dividerW, 5)) | |
divider.backgroundColor = UIColor.blackColor() | |
self.addSubview(divider) | |
let text = "\(numberFormatter.stringFromNumber(x / gradient.frame.size.width)!)" | |
let labelWidth = ChartUtils.textSize(text, font: ExamplesDefaults.labelFont).width | |
let label = UILabel() | |
label.center = CGPointMake(x - labelWidth / 2, 30) | |
label.font = ExamplesDefaults.labelFont | |
label.text = text | |
label.sizeToFit() | |
self.addSubview(label) | |
} | |
} | |
func colorForPercentage(percentage: CGFloat) -> UIColor { | |
let data = self.imgData | |
let xNotRounded = self.gradientImg.size.width * percentage | |
let x = 4 * (floor(abs(xNotRounded / 4))) | |
let pixelIndex = Int(x * 4) | |
let color = UIColor( | |
red: CGFloat(data[pixelIndex + 0]) / 255.0, | |
green: CGFloat(data[pixelIndex + 1]) / 255.0, | |
blue: CGFloat(data[pixelIndex + 2]) / 255.0, | |
alpha: CGFloat(data[pixelIndex + 3]) / 255.0 | |
) | |
return color | |
} | |
required init(coder aDecoder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment