BubbleExample.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. //
  2. // ScatterExample.swift
  3. // Examples
  4. //
  5. // Created by ischuetz on 16/05/15.
  6. // Copyright (c) 2015 ivanschuetz. All rights reserved.
  7. //
  8. import UIKit
  9. import CoreGraphics
  10. import SwiftCharts
  11. class BubbleExample: UIViewController {
  12. fileprivate var chart: Chart?
  13. fileprivate let colorBarHeight: CGFloat = 50
  14. fileprivate let useViewsLayer = true
  15. override func viewDidLoad() {
  16. super.viewDidLoad()
  17. let frame = ExamplesDefaults.chartFrame(view.bounds)
  18. let chartFrame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width, height: frame.size.height - colorBarHeight)
  19. let colorBar = ColorBar(frame: CGRect(x: 0, y: chartFrame.origin.y + chartFrame.size.height, width: view.frame.size.width, height: colorBarHeight), c1: UIColor.red, c2: UIColor.green)
  20. view.addSubview(colorBar)
  21. let labelSettings = ChartLabelSettings(font: ExamplesDefaults.labelFont)
  22. func toColor(_ percentage: Double) -> UIColor {
  23. return colorBar.colorForPercentage(percentage).withAlphaComponent(0.6)
  24. }
  25. let rawData: [(Double, Double, Double, UIColor)] = [
  26. (2, 2, 100, toColor(0)),
  27. (2.1, 5, 250, toColor(0)),
  28. (4, 4, 200, toColor(0.2)),
  29. (2.3, 5, 150, toColor(0.7)),
  30. (6, 7, 120, toColor(0.9)),
  31. (8, 3, 50, toColor(1)),
  32. (2, 4.5, 80, toColor(0.7)),
  33. (2, 5.2, 50, toColor(0.4)),
  34. (2, 4, 100, toColor(0.3)),
  35. (2.7, 5.5, 200, toColor(0.5)),
  36. (1.7, 2.8, 150, toColor(0.7)),
  37. (4.4, 8, 120, toColor(0.9)),
  38. (5, 6.3, 250, toColor(1)),
  39. (6, 8, 100, toColor(0)),
  40. (4, 8.5, 200, toColor(0.5)),
  41. (8, 5, 200, toColor(0.6)),
  42. (8.5, 10, 150, toColor(0.7)),
  43. (9, 11, 120, toColor(0.6)),
  44. (10, 6, 100, toColor(1)),
  45. (11, 7, 100, toColor(0)),
  46. (11, 4, 200, toColor(0.5)),
  47. (11.5, 10, 150, toColor(0.7)),
  48. (12, 7, 120, toColor(0.9)),
  49. (12, 9, 250, toColor(0.8))
  50. ]
  51. let chartPoints: [ChartPointBubble] = rawData.map{ChartPointBubble(x: ChartAxisValueDouble($0, labelSettings: labelSettings), y: ChartAxisValueDouble($1), diameterScalar: $2, bgColor: $3)}
  52. let xValues = stride(from: -2, through: 14, by: 2).map {ChartAxisValueInt($0, labelSettings: labelSettings)}
  53. let yValues = stride(from: -2, through: 12, by: 2).map {ChartAxisValueInt($0, labelSettings: labelSettings)}
  54. let xModel = ChartAxisModel(axisValues: xValues, axisTitleLabel: ChartAxisLabel(text: "Axis title", settings: labelSettings))
  55. let yModel = ChartAxisModel(axisValues: yValues, axisTitleLabel: ChartAxisLabel(text: "Axis title", settings: labelSettings.defaultVertical()))
  56. let chartSettings = ExamplesDefaults.chartSettingsWithPanZoom
  57. let coordsSpace = ChartCoordsSpaceLeftBottomSingleAxis(chartSettings: chartSettings, chartFrame: chartFrame, xModel: xModel, yModel: yModel)
  58. let (xAxisLayer, yAxisLayer, innerFrame) = (coordsSpace.xAxisLayer, coordsSpace.yAxisLayer, coordsSpace.chartInnerFrame)
  59. let bubbleLayer = bubblesLayer(xAxisLayer, yAxisLayer: yAxisLayer, chartInnerFrame: innerFrame, chartPoints: chartPoints)
  60. let guidelinesLayerSettings = ChartGuideLinesDottedLayerSettings(linesColor: UIColor.black, linesWidth: ExamplesDefaults.guidelinesWidth)
  61. let guidelinesLayer = ChartGuideLinesDottedLayer(xAxisLayer: xAxisLayer, yAxisLayer: yAxisLayer, settings: guidelinesLayerSettings)
  62. let guidelinesHighlightLayerSettings = ChartGuideLinesDottedLayerSettings(linesColor: UIColor.red, linesWidth: 1, dotWidth: 4, dotSpacing: 4)
  63. let guidelinesHighlightLayer = ChartGuideLinesForValuesDottedLayer(xAxis: xAxisLayer.axis, yAxis: yAxisLayer.axis, settings: guidelinesHighlightLayerSettings, axisValuesX: [ChartAxisValueDouble(0)], axisValuesY: [ChartAxisValueDouble(0)])
  64. let chart = Chart(
  65. frame: chartFrame,
  66. innerFrame: innerFrame,
  67. settings: chartSettings,
  68. layers: [
  69. xAxisLayer,
  70. yAxisLayer,
  71. guidelinesLayer,
  72. guidelinesHighlightLayer,
  73. bubbleLayer
  74. ]
  75. )
  76. view.addSubview(chart.view)
  77. self.chart = chart
  78. }
  79. // 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.
  80. // 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.
  81. fileprivate func bubblesLayer(_ xAxisLayer: ChartAxisLayer, yAxisLayer: ChartAxisLayer, chartInnerFrame: CGRect, chartPoints: [ChartPointBubble]) -> ChartLayer {
  82. let maxBubbleDiameter: Double = 30, minBubbleDiameter: Double = 2
  83. if useViewsLayer == true {
  84. let (minDiameterScalar, maxDiameterScalar): (Double, Double) = chartPoints.reduce((min: 0, max: 0)) {tuple, chartPoint in
  85. (min: min(tuple.min, chartPoint.diameterScalar), max: max(tuple.max, chartPoint.diameterScalar))
  86. }
  87. let diameterFactor = (maxBubbleDiameter - minBubbleDiameter) / (maxDiameterScalar - minDiameterScalar)
  88. return ChartPointsViewsLayer(xAxis: xAxisLayer.axis, yAxis: yAxisLayer.axis, chartPoints: chartPoints, viewGenerator: {(chartPointModel, layer, chart) -> UIView? in
  89. let diameter = CGFloat(chartPointModel.chartPoint.diameterScalar * diameterFactor)
  90. let circleView = ChartPointEllipseView(center: chartPointModel.screenLoc, diameter: diameter)
  91. circleView.fillColor = chartPointModel.chartPoint.bgColor
  92. circleView.borderColor = UIColor.black.withAlphaComponent(0.6)
  93. circleView.borderWidth = 1
  94. circleView.animDelay = Float(chartPointModel.index) * 0.2
  95. circleView.animDuration = 1.2
  96. circleView.animDamping = 0.4
  97. circleView.animInitSpringVelocity = 0.5
  98. return circleView
  99. })
  100. } else {
  101. return ChartPointsBubbleLayer(xAxis: xAxisLayer.axis, yAxis: yAxisLayer.axis, chartPoints: chartPoints)
  102. }
  103. }
  104. class ColorBar: UIView {
  105. let dividers: [CGFloat]
  106. let gradientImg: UIImage
  107. lazy private(set) var imgData: UnsafePointer<UInt8> = {
  108. let provider = self.gradientImg.cgImage!.dataProvider
  109. let pixelData = provider!.data
  110. return CFDataGetBytePtr(pixelData)
  111. }()
  112. init(frame: CGRect, c1: UIColor, c2: UIColor) {
  113. let gradient: CAGradientLayer = CAGradientLayer()
  114. gradient.frame = CGRect(x: 0, y: 0, width: frame.width, height: 30)
  115. gradient.colors = [UIColor.blue.cgColor, UIColor.cyan.cgColor, UIColor.yellow.cgColor, UIColor.red.cgColor]
  116. gradient.startPoint = CGPoint(x: 0, y: 0.5)
  117. gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
  118. let imgHeight = 1
  119. let imgWidth = Int(gradient.bounds.size.width)
  120. let bitmapBytesPerRow = imgWidth * 4
  121. let colorSpace = CGColorSpaceCreateDeviceRGB()
  122. let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue).rawValue
  123. let context = CGContext (data: nil,
  124. width: imgWidth,
  125. height: imgHeight,
  126. bitsPerComponent: 8,
  127. bytesPerRow: bitmapBytesPerRow,
  128. space: colorSpace,
  129. bitmapInfo: bitmapInfo)
  130. UIGraphicsBeginImageContext(gradient.bounds.size)
  131. gradient.render(in: context!)
  132. let gradientImg = UIImage(cgImage: context!.makeImage()!)
  133. UIGraphicsEndImageContext()
  134. self.gradientImg = gradientImg
  135. let segmentSize = gradient.frame.size.width / 6
  136. dividers = Array(stride(from: segmentSize, through: gradient.frame.size.width, by: segmentSize))
  137. super.init(frame: frame)
  138. layer.insertSublayer(gradient, at: 0)
  139. let numberFormatter = NumberFormatter()
  140. numberFormatter.maximumFractionDigits = 2
  141. for x in stride(from: segmentSize, through: gradient.frame.size.width - 1, by: segmentSize) {
  142. let dividerW: CGFloat = 1
  143. let divider = UIView(frame: CGRect(x: x - dividerW / 2, y: 25, width: dividerW, height: 5))
  144. divider.backgroundColor = UIColor.black
  145. addSubview(divider)
  146. let text = "\(numberFormatter.string(from: NSNumber(value: Float(x / gradient.frame.size.width)))!)"
  147. let labelWidth = text.width(ExamplesDefaults.labelFont)
  148. let label = UILabel()
  149. label.center = CGPoint(x: x - labelWidth / 2, y: 30)
  150. label.font = ExamplesDefaults.labelFont
  151. label.text = text
  152. label.sizeToFit()
  153. addSubview(label)
  154. }
  155. }
  156. func colorForPercentage(_ percentage: Double) -> UIColor {
  157. let data = imgData
  158. let xNotRounded = gradientImg.size.width * CGFloat(percentage)
  159. let x = 4 * (floor(abs(xNotRounded / 4)))
  160. let pixelIndex = Int(x * 4)
  161. let color = UIColor(
  162. red: CGFloat(data[pixelIndex + 0]) / 255.0,
  163. green: CGFloat(data[pixelIndex + 1]) / 255.0,
  164. blue: CGFloat(data[pixelIndex + 2]) / 255.0,
  165. alpha: CGFloat(data[pixelIndex + 3]) / 255.0
  166. )
  167. return color
  168. }
  169. required init(coder aDecoder: NSCoder) {
  170. fatalError("init(coder:) has not been implemented")
  171. }
  172. }
  173. }