From 4635820530eeae4f0ec6a49a58306d85740c46f0 Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Tue, 2 Aug 2022 11:05:28 +0200 Subject: [PATCH] feat: added different scaling types to the charts (none, square, cube, logarithmic) (#844) --- Kit/plugins/Charts.swift | 75 ++++++++++++++++++++++++++++++++++++---- Kit/types.swift | 27 +++++++++++++++ 2 files changed, 95 insertions(+), 7 deletions(-) diff --git a/Kit/plugins/Charts.swift b/Kit/plugins/Charts.swift index 6c63918c..47cba920 100644 --- a/Kit/plugins/Charts.swift +++ b/Kit/plugins/Charts.swift @@ -30,6 +30,50 @@ public struct circle_segment { } } +private func scaleValue(scale: Scale = .linear, value: Double, maxValue: Double, maxHeight: CGFloat) -> CGFloat { + var value = value + var localMaxValue = maxValue + var y = value * maxHeight + + switch scale { + case .square: + if value > 0 { + value = sqrt(value) + } + if localMaxValue > 0 { + localMaxValue = sqrt(maxValue) + } + case .cube: + if value > 0 { + value = cbrt(value) + } + if localMaxValue > 0 { + localMaxValue = cbrt(maxValue) + } + case .logarithmic: + if value > 0 { + value = log(value*100) + } + if localMaxValue > 0 { + localMaxValue = log(maxValue*100) + } + default: break + } + + if value < 0 { + value = 0 + } + if localMaxValue <= 0 { + localMaxValue = 1 + } + + if scale != .none { + y = (maxHeight * value)/localMaxValue + } + + return y +} + public class LineChartView: NSView { public var id: String = UUID().uuidString @@ -38,12 +82,13 @@ public class LineChartView: NSView { public var transparent: Bool = true public var color: NSColor = controlAccentColor public var suffix: String = "%" + public var scale: Scale = .none private var cursor: NSPoint? = nil private var stop: Bool = false public init(frame: NSRect, num: Int) { - self.points = Array(repeating: 0.01, count: num) + self.points = Array(repeating: 0, count: num) super.init(frame: frame) @@ -69,6 +114,7 @@ public class LineChartView: NSView { if self.stop { points = self.shadowPoints } + guard let maxValue = points.max() else { return } if points.isEmpty { return @@ -88,10 +134,10 @@ public class LineChartView: NSView { let xRatio: CGFloat = self.frame.size.width / CGFloat(points.count) let list = points.enumerated().compactMap { (i: Int, v: Double) -> (value: Double, point: CGPoint) in - let x: CGFloat = (CGFloat(i) * xRatio) + dirtyRect.origin.x - let y = CGFloat((CGFloat(truncating: v as NSNumber) * height)) + dirtyRect.origin.y + offset - - return (v, CGPoint(x: x, y: y)) + return (v, CGPoint( + x: (CGFloat(i) * xRatio) + dirtyRect.origin.x, + y: scaleValue(scale: self.scale, value: v, maxValue: maxValue, maxHeight: height) + dirtyRect.origin.y + offset + )) } let line = NSBezierPath() @@ -210,6 +256,13 @@ public class LineChartView: NSView { } } + public func setScale(_ newScale: Scale) { + self.scale = newScale + if self.window?.isVisible ?? false { + self.display() + } + } + public override func mouseEntered(with event: NSEvent) { self.cursor = convert(event.locationInWindow, from: nil) self.needsDisplay = true @@ -246,6 +299,7 @@ public class NetworkChartView: NSView { public var points: [(Double, Double)] private var minMax: Bool = false + private var scale: Scale = .none public init(frame: NSRect, num: Int, minMax: Bool = true) { self.minMax = minMax @@ -282,10 +336,10 @@ public class NetworkChartView: NSView { return (CGFloat(point) * xRatio) + (dirtyRect.origin.x - lineWidth) } let uploadYPoint = { (point: Int) -> CGFloat in - return CGFloat((points[point].0 * Double(dirtyRect.height/2)) / uploadMax) + dirtyRect.origin.y + dirtyRect.height/2 - offset + return scaleValue(scale: self.scale, value: points[point].0, maxValue: uploadMax, maxHeight: dirtyRect.height/2) + (dirtyRect.origin.y + dirtyRect.height/2 - offset) } let downloadYPoint = { (point: Int) -> CGFloat in - return (dirtyRect.height/2 + dirtyRect.origin.y + offset) - CGFloat((points[point].1 * Double(dirtyRect.height/2)) / downloadMax) + return (dirtyRect.height/2 + dirtyRect.origin.y + offset) - scaleValue(scale: self.scale, value: points[point].1, maxValue: downloadMax, maxHeight: dirtyRect.height/2) } let uploadlinePath = NSBezierPath() @@ -369,6 +423,13 @@ public class NetworkChartView: NSView { self.points.replaceSubrange(Range(uncheckedBounds: (lower: origin.count, upper: num)), with: origin) } } + + public func setScale(_ newScale: Scale) { + self.scale = newScale + if self.window?.isVisible ?? false { + self.display() + } + } } public class PieChartView: NSView { diff --git a/Kit/types.swift b/Kit/types.swift index baaf7f20..708f9280 100644 --- a/Kit/types.swift +++ b/Kit/types.swift @@ -243,3 +243,30 @@ public let notificationLevels: [KeyValue_t] = [ KeyValue_t(key: "97%", value: "97%"), KeyValue_t(key: "100%", value: "100%") ] + +public struct Scale: KeyValue_p, Equatable { + public let key: String + public let value: String + public var additional: Any? + + public static func == (lhs: Scale, rhs: Scale) -> Bool { + return lhs.key == rhs.key + } +} + +extension Scale: CaseIterable { + public static var none: Scale { return Scale(key: "none", value: "None") } + public static var separator: Scale { return Scale(key: "separator", value: "separator") } + public static var linear: Scale { return Scale(key: "linear", value: "Linear") } + public static var square: Scale { return Scale(key: "square", value: "Square") } + public static var cube: Scale { return Scale(key: "cube", value: "Cube") } + public static var logarithmic: Scale { return Scale(key: "logarithmic", value: "Logarithmic") } + + public static var allCases: [Scale] { + return [.none, .separator, .linear, .square, .cube, .logarithmic] + } + + public static func fromString(_ key: String, defaultValue: Scale = .linear) -> Scale { + return Scale.allCases.first{ $0.key == key } ?? defaultValue + } +}