@@ -522,17 +522,26 @@ public class NetworkChartView: NSView {
522522}
523523
524524public 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
0 commit comments