DOCSIS-Toolkit/Graphic Analysis 2/FrequencyGraph.swift

287 lines
11 KiB
Swift

//
// GraphView.swift
// Docsis Toolkit
//
// Created by Kilian Hofmann on 20.06.17.
// Copyright © 2017 Kilian Hofmann. All rights reserved.
//
import Cocoa
class FrequencyGraph: NSView {
let opQueue: OperationQueue = OperationQueue()
// Constants
static let graphSizeWithSeparator: Int = 109
static let graphSize: Int = 108
static let maxGraphSize: Int = 17300
let zeroOffset: Double = 37
// Path related
var path: NSBezierPath = NSBezierPath.init()
var threshold: NSBezierPath = NSBezierPath.init()
var upstream: Bool = false
var lastTime: Int = 0
var lastTimeThreshold: Int = 0
var timePaths: [NSBezierPath] = []
var lossPaths: [NSBezierPath] = []
var offset: Int = 0
// Popover
let popover: NSPopover = NSPopover()
// Data
var data: [String] = []
var dataLoss: [String] = []
var frequency: Double = 0.0
// Cursor
var cursor: NSBezierPath = NSBezierPath.init()
// Superview
var content: FlippedView!
// Indicator
var indicator: NSProgressIndicator!
init(upstream: Bool, frame: CGRect, superview: FlippedView, indicator: NSProgressIndicator) {
super.init(frame: frame)
self.upstream = upstream
self.indicator = indicator
let tracking: NSTrackingArea = NSTrackingArea(rect: self.bounds, options: [.mouseEnteredAndExited, .mouseMoved, .activeInActiveApp, .inVisibleRect], owner: self, userInfo: nil)
self.addTrackingArea(tracking)
popover.contentViewController = GraphDetailsController()
self.content = superview
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func draw(_ dirtyRect: NSRect) {
NSColor.white.setFill()
__NSRectFill(dirtyRect)
threshold.lineWidth = 0.5
if upstream {
NSColor.red.set()
threshold.stroke()
} else {
NSColor.blue.set()
threshold.stroke()
}
NSColor.black.set()
path.lineWidth = 1
path.stroke()
NSColor(red: 0, green: 0, blue: 0, alpha: 0.3).set()
cursor.lineWidth = 2
cursor.stroke()
for index in 0..<timePaths.count {
timePaths[index].lineWidth = 0.5
timePaths[index].stroke()
}
for index in 0..<lossPaths.count {
NSColor(red: 1, green: 0, blue: 0, alpha: 0.2).set()
lossPaths[index].lineWidth = 2
lossPaths[index].stroke()
}
}
override var isFlipped:Bool {
get {
return false
}
}
func initView(operation: GraphLoadOperation) {
// Setup Indicator
indicator.doubleValue = 0
indicator.isHidden = false
indicator.minValue = 0
indicator.maxValue = Double(data.count)
// Zero line
let zero: NSBezierPath = NSBezierPath()
zero.move(to: NSPoint(x: 5, y: zeroOffset))
zero.line(to: NSPoint(x: Int(FrequencyGraph.maxGraphSize-5), y: Int(zeroOffset)))
self.timePaths.append(zero)
// Add data points
var lastWasSkipped: Bool = false
//
var lastLossIndex: Int = 0
for point in 0..<data.count {
if operation.isCancelled {
return
}
// Calulate offsets if start
if point == 0 {
// Get offset
offset = 0
let time: String = data[point].components(separatedBy: ";")[0]
let hour: Int = Int(time.substring(to: time.index(time.startIndex, offsetBy: 2)))!
let tensSeconds: Int = Int((time as NSString).substring(with: NSMakeRange(6, 1)))!
offset = ((hour*60 + Int((time as NSString).substring(with: NSMakeRange(3, 2)))!) * 6 + tensSeconds) * 2
// Set offset
lastTime = offset
lastTimeThreshold = offset
}
// Get values
let level: Double? = Double(data[point].components(separatedBy: ";")[1])
let threshold: Double? = Double(data[point].components(separatedBy: ";")[2])
// Set path, skip if empty values (-)
if level == nil || threshold == nil {
skipPath()
lastWasSkipped = true
} else {
if lastWasSkipped {
restartPath(level!, threshold_level: threshold!)
lastWasSkipped = false
} else {
addPath(level!, threshold_level: threshold!)
}
}
// Set connection loss zones
for loss in lastLossIndex..<dataLoss.count {
let time1: String = data[point].components(separatedBy: ";")[0]
let hour1: Int = Int(time1.substring(to: time1.index(time1.startIndex, offsetBy: 2)))!
let minute1: Int = Int((time1 as NSString).substring(with: NSMakeRange(3, 2)))!
let time2: String = dataLoss[loss]
let hour2: Int = Int(time2.substring(to: time2.index(time2.startIndex, offsetBy: 2)))!
let minute2: Int = Int((time2 as NSString).substring(with: NSMakeRange(3, 2)))!
if hour1 < hour2 { break }
else if hour1 == hour2 && minute1 < minute2 { break }
if hour1 == hour2 && minute1 == minute2 {
let lossPath: NSBezierPath = NSBezierPath()
lossPath.move(to: NSPoint(x: CGFloat(10 + point*2 + offset), y: CGFloat(17)))
lossPath.line(to: NSPoint(x: CGFloat(10 + point*2 + offset), y: CGFloat(FrequencyGraph.graphSize)))
lossPaths.append(lossPath)
lastLossIndex = loss
}
}
// Set time scale
if (point+offset/2) % 60 == 0 {
// Time label
let time: NSTextField = NSTextField(frame: NSMakeRect(CGFloat(10 + point*2 + offset), 0, 57, 17))
time.drawsBackground = false
time.isBezeled = false
time.isBordered = false
time.isSelectable = false
time.stringValue = data[point].components(separatedBy: ";")[0]
addSubview(time)
// Time line
let timePath: NSBezierPath = NSBezierPath()
timePath.move(to: NSPoint(x: CGFloat(10 + point*2 + offset), y: CGFloat(5)))
timePath.line(to: NSPoint(x: CGFloat(10 + point*2 + offset), y: CGFloat(107)))
timePaths.append(timePath)
}
if (point+offset/2) % 100 == 0 {
OperationQueue.main.addOperation {
self.indicator.increment(by: 100)
}
}
}
// Put view in display
OperationQueue.main.addOperation {
self.indicator.isHidden = true
self.content.addSubview(self)
self.setNeedsDisplay(self.bounds)
}
OperationQueue.main.waitUntilAllOperationsAreFinished()
}
// MARK: - Path related
func addPath(_ level: Double, threshold_level: Double) {
if path.elementCount == 0 {
path.move(to: NSMakePoint(CGFloat(10 + lastTime), CGFloat(level + zeroOffset)))
} else {
path.line(to: NSMakePoint(CGFloat(10 + lastTime), CGFloat(level + zeroOffset)))
}
lastTime += 2
if threshold.elementCount == 0 {
threshold.move(to: NSMakePoint(CGFloat(10 + lastTimeThreshold), CGFloat(threshold_level + zeroOffset)))
} else {
threshold.line(to: NSMakePoint(CGFloat(10 + lastTimeThreshold), CGFloat(threshold_level + zeroOffset)))
}
lastTimeThreshold += 2
}
func skipPath() {
path.move(to: NSMakePoint(CGFloat(10 + lastTime), CGFloat(zeroOffset)))
lastTime += 2
threshold.move(to: NSMakePoint(CGFloat(10 + lastTimeThreshold), CGFloat(zeroOffset)))
lastTimeThreshold += 2
}
func restartPath(_ level: Double, threshold_level: Double) {
path.move(to: NSMakePoint(CGFloat(10 + lastTime), CGFloat(level + 37)))
lastTime += 2
threshold.move(to: NSMakePoint(CGFloat(10 + lastTimeThreshold), CGFloat(threshold_level + zeroOffset)))
lastTimeThreshold += 2
}
// MARK: - Mouse Events
override func mouseMoved(with event: NSEvent) {
cursor.removeAllPoints()
popover.performClose(self)
let x: Int = Int(event.locationInWindow.x - 20 + (self.superview!.superview!.superview! as! NSScrollView).documentVisibleRect.origin.x)
cursor.move(to: NSPoint(x: x - x%2, y: 0))
cursor.line(to: NSPoint(x: x - x%2, y: FrequencyGraph.graphSize))
setNeedsDisplay(self.bounds)
}
override func mouseExited(with event: NSEvent) {
cursor.removeAllPoints()
popover.performClose(self)
setNeedsDisplay(self.bounds)
}
override func mouseDown(with event: NSEvent) {
let x: Int = Int(event.locationInWindow.x - 20 + (self.superview!.superview!.superview! as! NSScrollView).documentVisibleRect.origin.x)
if 10 <= x && (x-10)/2 - offset/2 < data.count && (x-10)/2 - offset/2 >= 0 {
(popover.contentViewController! as! GraphDetailsController).freq_str = "\(roundToPlaces(1, input: frequency)) MHz"
(popover.contentViewController! as! GraphDetailsController).time_str = "\(data[(x-10) / 2 - offset / 2].components(separatedBy: ";")[0])"
(popover.contentViewController! as! GraphDetailsController).power_str = "\(data[(x-10) / 2 - offset / 2].components(separatedBy: ";")[1]) dBmV"
if upstream {
(popover.contentViewController! as! GraphDetailsController).snr_str = "\(data[(x-10) / 2 - offset / 2].components(separatedBy: ";")[2]) dBmV"
(popover.contentViewController! as! GraphDetailsController).ranging_str = "\(data[(x-10) / 2 - offset / 2].components(separatedBy: ";")[3])"
} else {
(popover.contentViewController! as! GraphDetailsController).snr_str = "\(data[(x-10) / 2 - offset / 2].components(separatedBy: ";")[2]) dB"
}
popover.show(relativeTo: NSMakeRect(CGFloat(x), 0, 2, CGFloat(FrequencyGraph.graphSize)), of: self, preferredEdge: .minY)
}
}
override func scrollWheel(with event: NSEvent) {
super.scrollWheel(with: event)
cursor.removeAllPoints()
popover.performClose(self)
setNeedsDisplay(self.bounds)
}
// MARK: - Helper
func roundToPlaces(_ places:Int, input: Double) -> Double {
let divisor = pow(10.0, Double(places))
return round(input * divisor) / divisor
}
}