Skip to content

Commit 765ead9

Browse files
committed
feat: replaced HalfCircleGraphView with PieChart
1 parent 6f2c2c6 commit 765ead9

File tree

4 files changed

+173
-231
lines changed

4 files changed

+173
-231
lines changed

Kit/plugins/Charts.swift

Lines changed: 157 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -522,17 +522,26 @@ public class NetworkChartView: NSView {
522522
}
523523

524524
public class PieChartView: NSView {
525+
public var id: String = UUID().uuidString
526+
525527
private var filled: Bool = false
526528
private var drawValue: Bool = false
529+
private var drawNeedle: Bool = false
530+
private var openCircle: Bool = false
527531
private var nonActiveSegmentColor: NSColor = NSColor.lightGray
528532

529533
private var value: Double? = nil
534+
private var text: String? = nil
535+
private var activeSegment: Int? = nil
530536
private var segments: [ColorValue] = []
537+
public var color: NSColor = NSColor.systemBlue
531538
private var queue: DispatchQueue = DispatchQueue(label: "eu.exelban.Stats.charts.pie")
532539

533-
public init(frame: NSRect = .zero, segments: [ColorValue] = [], filled: Bool = false, drawValue: Bool = false) {
540+
public init(frame: NSRect = .zero, segments: [ColorValue] = [], filled: Bool = false, drawValue: Bool = false, drawNeedle: Bool = false, openCircle: Bool = false) {
534541
self.filled = filled
535542
self.drawValue = drawValue
543+
self.drawNeedle = drawNeedle
544+
self.openCircle = openCircle
536545
self.segments = segments
537546

538547
super.init(frame: frame)
@@ -547,22 +556,44 @@ public class PieChartView: NSView {
547556
public override func draw(_ rect: CGRect) {
548557
var filled: Bool = false
549558
var drawValue: Bool = false
559+
var drawNeedle: Bool = false
560+
var openCircle: Bool = false
550561
var nonActiveSegmentColor: NSColor = NSColor.lightGray
551562
var value: Double? = nil
563+
var text: String? = nil
564+
var activeSegment: Int? = nil
552565
var segments: [ColorValue] = []
566+
var color: NSColor = NSColor.systemBlue
553567
self.queue.sync {
554568
filled = self.filled
555569
drawValue = self.drawValue
570+
drawNeedle = self.drawNeedle
571+
openCircle = self.openCircle
556572
nonActiveSegmentColor = self.nonActiveSegmentColor
557573
value = self.value
574+
text = self.text
575+
activeSegment = self.activeSegment
558576
segments = self.segments
577+
color = self.color
559578
}
560579

561580
let arcWidth: CGFloat = filled ? min(self.frame.width, self.frame.height) / 2 : 7
562-
let fullCircle = 2 * CGFloat.pi
563-
let totalAmount = segments.reduce(0) { $0 + $1.value }
564-
if totalAmount < 1 {
565-
segments.append(ColorValue(Double(1-totalAmount), color: nonActiveSegmentColor.withAlphaComponent(0.5)))
581+
let fullCircle: CGFloat = 2 * CGFloat.pi
582+
let arcSpan: CGFloat = openCircle ? (3/2) * CGFloat.pi : fullCircle
583+
584+
if openCircle {
585+
if segments.isEmpty {
586+
segments = [ColorValue(value ?? 0, color: color)]
587+
}
588+
let totalAmount = segments.reduce(0) { $0 + $1.value }
589+
if totalAmount < 1 {
590+
segments.append(ColorValue(Double(1-totalAmount), color: NSColor.lightGray.withAlphaComponent(0.5)))
591+
}
592+
} else {
593+
let totalAmount = segments.reduce(0) { $0 + $1.value }
594+
if totalAmount < 1 {
595+
segments.append(ColorValue(Double(1-totalAmount), color: nonActiveSegmentColor.withAlphaComponent(0.5)))
596+
}
566597
}
567598

568599
let centerPoint = CGPoint(x: self.frame.width/2, y: self.frame.height/2)
@@ -572,24 +603,115 @@ public class PieChartView: NSView {
572603
context.setShouldAntialias(true)
573604

574605
context.setLineWidth(arcWidth)
575-
context.setLineCap(.butt)
576-
577-
let startAngle: CGFloat = CGFloat.pi/2
578-
var previousAngle = startAngle
606+
context.setLineCap(openCircle ? .round : .butt)
579607

580-
for segment in segments.reversed() {
581-
let currentAngle: CGFloat = previousAngle + (CGFloat(segment.value) * fullCircle)
608+
if openCircle {
609+
let startAngle: CGFloat = CGFloat.pi + CGFloat.pi/4
610+
var previousAngle = startAngle
582611

583-
if let color = segment.color {
584-
context.setStrokeColor(color.cgColor)
612+
for segment in segments {
613+
let currentAngle: CGFloat = previousAngle - (CGFloat(segment.value) * arcSpan)
614+
615+
if let color = segment.color {
616+
context.setStrokeColor(color.cgColor)
617+
}
618+
context.addArc(center: centerPoint, radius: radius, startAngle: previousAngle, endAngle: currentAngle, clockwise: true)
619+
context.strokePath()
620+
621+
previousAngle = currentAngle
585622
}
586-
context.addArc(center: centerPoint, radius: radius, startAngle: previousAngle, endAngle: currentAngle, clockwise: false)
587-
context.strokePath()
623+
} else {
624+
let startAngle: CGFloat = CGFloat.pi/2
625+
var previousAngle = startAngle
588626

589-
previousAngle = currentAngle
627+
for segment in segments.reversed() {
628+
let currentAngle: CGFloat = previousAngle + (CGFloat(segment.value) * fullCircle)
629+
630+
if let color = segment.color {
631+
context.setStrokeColor(color.cgColor)
632+
}
633+
context.addArc(center: centerPoint, radius: radius, startAngle: previousAngle, endAngle: currentAngle, clockwise: false)
634+
context.strokePath()
635+
636+
previousAngle = currentAngle
637+
}
638+
}
639+
640+
if drawNeedle, let activeSegment = activeSegment, !segments.isEmpty {
641+
let needleEndSize: CGFloat = 2
642+
let startAngle: CGFloat = CGFloat.pi + CGFloat.pi/4
643+
let idx = min(activeSegment, segments.count - 1)
644+
var needleValue: CGFloat = 0
645+
for i in 0..<idx {
646+
needleValue += CGFloat(segments[i].value)
647+
}
648+
needleValue += CGFloat(segments[idx].value) / 2
649+
let needleAngle = startAngle - needleValue * arcSpan
650+
let needleLength = radius - arcWidth/2
651+
652+
let tip = CGPoint(
653+
x: centerPoint.x + needleLength * cos(needleAngle),
654+
y: centerPoint.y + needleLength * sin(needleAngle)
655+
)
656+
let perpAngle = needleAngle + CGFloat.pi/2
657+
let base1 = CGPoint(
658+
x: centerPoint.x + needleEndSize * cos(perpAngle),
659+
y: centerPoint.y + needleEndSize * sin(perpAngle)
660+
)
661+
let base2 = CGPoint(
662+
x: centerPoint.x - needleEndSize * cos(perpAngle),
663+
y: centerPoint.y - needleEndSize * sin(perpAngle)
664+
)
665+
666+
let needlePath = NSBezierPath()
667+
needlePath.move(to: tip)
668+
needlePath.line(to: base1)
669+
needlePath.line(to: base2)
670+
needlePath.close()
671+
672+
let needleCirclePath = NSBezierPath(
673+
roundedRect: NSRect(
674+
x: centerPoint.x - needleEndSize,
675+
y: centerPoint.y - needleEndSize,
676+
width: needleEndSize * 2,
677+
height: needleEndSize * 2
678+
),
679+
xRadius: needleEndSize * 2,
680+
yRadius: needleEndSize * 2
681+
)
682+
needleCirclePath.close()
683+
684+
NSColor.systemBlue.setFill()
685+
needlePath.fill()
686+
needleCirclePath.fill()
590687
}
591688

592-
if let value = value, drawValue {
689+
if drawNeedle, let activeSegment = activeSegment {
690+
let stringAttributes = [
691+
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .regular),
692+
NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor,
693+
NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
694+
]
695+
696+
let text = "\(activeSegment+1)"
697+
let width: CGFloat = text.widthOfString(usingFont: NSFont.systemFont(ofSize: 9))
698+
let rect = CGRect(x: (self.frame.width-width)/2, y: (self.frame.height-26)/2, width: width, height: 12)
699+
let str = NSAttributedString.init(string: text, attributes: stringAttributes)
700+
str.draw(with: rect)
701+
} else if let text = text {
702+
let style = NSMutableParagraphStyle()
703+
style.alignment = .center
704+
let stringAttributes = [
705+
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 10, weight: .regular),
706+
NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor,
707+
NSAttributedString.Key.paragraphStyle: style
708+
]
709+
710+
let width: CGFloat = text.widthOfString(usingFont: NSFont.systemFont(ofSize: 10))
711+
let rect = CGRect(x: ((self.frame.width-width)/2)-0.5, y: (self.frame.height-6)/2, width: width, height: 13)
712+
let str = NSAttributedString.init(string: text, attributes: stringAttributes)
713+
str.draw(with: rect)
714+
} else if let value = value, drawValue {
593715
let stringAttributes = [
594716
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 15, weight: .regular),
595717
NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor,
@@ -606,147 +728,63 @@ public class PieChartView: NSView {
606728

607729
public func setValue(_ value: Double) {
608730
self.queue.async(flags: .barrier) {
609-
self.value = value
731+
self.value = self.openCircle ? (value > 1 ? value/100 : value) : value
610732
}
611733
if self.window?.isVisible ?? false {
612734
self.display()
613735
}
614736
}
615737

616-
public func setSegments(_ segments: [ColorValue]) {
738+
public func setActiveSegment(_ index: Int) {
617739
self.queue.async(flags: .barrier) {
618-
self.segments = segments
740+
self.activeSegment = index
619741
}
620742
if self.window?.isVisible ?? false {
621743
self.display()
622744
}
623745
}
624746

625-
public func setFrame(_ frame: NSRect) {
626-
var original = self.frame
627-
original = frame
628-
self.frame = original
629-
}
630-
631-
public func setNonActiveSegmentColor(_ newColor: NSColor) {
632-
guard self.nonActiveSegmentColor != newColor else { return }
747+
public func setText(_ value: String) {
633748
self.queue.async(flags: .barrier) {
634-
self.nonActiveSegmentColor = newColor
749+
self.text = value
635750
}
636751
if self.window?.isVisible ?? false {
637752
self.display()
638753
}
639754
}
640-
}
641-
642-
public class HalfCircleGraphView: NSView {
643-
public var id: String = UUID().uuidString
644-
645-
private var value: Double = 0.0
646-
private var text: String? = nil
647-
private var queue: DispatchQueue = DispatchQueue(label: "eu.exelban.Stats.charts.halfcircle")
648-
649-
public var color: NSColor = NSColor.systemBlue
650-
651-
public override init(frame: NSRect) {
652-
super.init(frame: frame)
653-
self.setAccessibilityElement(true)
654-
}
655-
656-
required init?(coder: NSCoder) {
657-
fatalError("init(coder:) has not been implemented")
658-
}
659-
660-
public override func draw(_ rect: CGRect) {
661-
var value: Double = 0.0
662-
var text: String? = nil
663-
var color: NSColor = NSColor.systemBlue
664-
self.queue.sync {
665-
value = self.value
666-
text = self.text
667-
color = self.color
668-
}
669-
670-
let arcWidth: CGFloat = 7.0
671-
let radius = (min(self.frame.width, self.frame.height) - arcWidth) / 2
672-
let centerPoint = CGPoint(x: self.frame.width/2, y: self.frame.height/2)
673-
674-
guard let context = NSGraphicsContext.current?.cgContext else { return }
675-
context.setShouldAntialias(true)
676-
677-
context.setLineWidth(arcWidth)
678-
context.setLineCap(.round)
679-
680-
var segments: [ColorValue] = [
681-
ColorValue(value, color: color)
682-
]
683-
if value < 1 {
684-
segments.append(ColorValue(Double(1-value), color: NSColor.lightGray.withAlphaComponent(0.5)))
685-
}
686-
687-
let startAngle: CGFloat = -(1/4)*CGFloat.pi
688-
let endCircle: CGFloat = (7/4)*CGFloat.pi - (1/4)*CGFloat.pi
689-
var previousAngle = startAngle
690-
691-
context.saveGState()
692-
context.translateBy(x: self.frame.width, y: 0)
693-
context.scaleBy(x: -1, y: 1)
694-
695-
for segment in segments {
696-
let currentAngle: CGFloat = previousAngle + (CGFloat(segment.value) * endCircle)
697-
698-
if let color = segment.color {
699-
context.setStrokeColor(color.cgColor)
700-
}
701-
context.addArc(center: centerPoint, radius: radius, startAngle: previousAngle, endAngle: currentAngle, clockwise: false)
702-
context.strokePath()
703-
704-
previousAngle = currentAngle
705-
}
706-
707-
context.restoreGState()
708-
709-
if let text = text {
710-
let style = NSMutableParagraphStyle()
711-
style.alignment = .center
712-
let stringAttributes = [
713-
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 10, weight: .regular),
714-
NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor,
715-
NSAttributedString.Key.paragraphStyle: style
716-
]
717-
718-
let width: CGFloat = text.widthOfString(usingFont: NSFont.systemFont(ofSize: 10))
719-
let rect = CGRect(x: ((self.frame.width-width)/2)-0.5, y: (self.frame.height-6)/2, width: width, height: 13)
720-
let str = NSAttributedString.init(string: text, attributes: stringAttributes)
721-
str.draw(with: rect)
722-
}
723-
}
724755

725-
public func setValue(_ value: Double) {
756+
public func setSegments(_ segments: [ColorValue]) {
726757
self.queue.async(flags: .barrier) {
727-
self.value = value > 1 ? value/100 : value
758+
self.segments = segments
728759
}
729760
if self.window?.isVisible ?? false {
730761
self.display()
731762
}
732763
}
733764

734-
public func setText(_ value: String) {
765+
public func setFrame(_ frame: NSRect) {
766+
var original = self.frame
767+
original = frame
768+
self.frame = original
769+
}
770+
771+
public func setNonActiveSegmentColor(_ newColor: NSColor) {
772+
guard self.nonActiveSegmentColor != newColor else { return }
735773
self.queue.async(flags: .barrier) {
736-
self.text = value
774+
self.nonActiveSegmentColor = newColor
737775
}
738776
if self.window?.isVisible ?? false {
739777
self.display()
740778
}
741779
}
742780
}
743781

744-
internal class TachometerGraphView: NSView {
782+
public class TachometerGraphView: NSView {
745783
private var filled: Bool
746784
private var segments: [ColorValue]
747785
private var queue: DispatchQueue = DispatchQueue(label: "eu.exelban.Stats.charts.tachometer")
748786

749-
internal init(frame: NSRect, segments: [ColorValue], filled: Bool = true) {
787+
public init(frame: NSRect = .zero, segments: [ColorValue], filled: Bool = true) {
750788
self.filled = filled
751789
self.segments = segments
752790

Modules/CPU/popup.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ internal class Popup: PopupWrapper {
8181
private var lineChart: LineChartView? = nil
8282
private var columnChart: ColumnChartView? = nil
8383
private var circle: PieChartView? = nil
84-
private var temperatureCircle: HalfCircleGraphView? = nil
85-
private var frequencyCircle: HalfCircleGraphView? = nil
84+
private var temperatureCircle: PieChartView? = nil
85+
private var frequencyCircle: PieChartView? = nil
8686
private var initialized: Bool = false
8787
private var initializedTemperature: Bool = false
8888
private var initializedFrequency: Bool = false
@@ -206,12 +206,12 @@ internal class Popup: PopupWrapper {
206206
self.circle!.toolTip = localizedString("CPU usage")
207207
usage.addSubview(self.circle!)
208208

209-
self.temperatureCircle = HalfCircleGraphView(frame: NSRect(x: 0, y: 0, width: temperature.frame.width, height: temperature.frame.height))
209+
self.temperatureCircle = PieChartView(frame: NSRect(x: 0, y: 0, width: temperature.frame.width, height: temperature.frame.height), openCircle: true)
210210
self.temperatureCircle!.toolTip = localizedString("CPU temperature")
211211
(self.temperatureCircle! as NSView).isHidden = true
212212
temperature.addSubview(self.temperatureCircle!)
213213

214-
self.frequencyCircle = HalfCircleGraphView(frame: NSRect(x: 0, y: 0, width: frequency.frame.width, height: frequency.frame.height))
214+
self.frequencyCircle = PieChartView(frame: NSRect(x: 0, y: 0, width: frequency.frame.width, height: frequency.frame.height), openCircle: true)
215215
self.frequencyCircle!.toolTip = localizedString("CPU frequency")
216216
(self.frequencyCircle! as NSView).isHidden = true
217217
frequency.addSubview(self.frequencyCircle!)

0 commit comments

Comments
 (0)