mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
fix: fixed charts that causes crashes (#2550)
This commit is contained in:
@@ -74,12 +74,17 @@ internal func scaleValue(scale: Scale = .linear, value: Double, maxValue: Double
|
||||
}
|
||||
|
||||
private func drawToolTip(_ frame: NSRect, _ point: CGPoint, _ size: CGSize, value: String, subtitle: String? = nil) {
|
||||
guard !value.isEmpty else { return }
|
||||
|
||||
let style = NSMutableParagraphStyle()
|
||||
style.alignment = .left
|
||||
var position: CGPoint = point
|
||||
let textHeight: CGFloat = subtitle != nil ? 22 : 12
|
||||
let valueOffset: CGFloat = subtitle != nil ? 11 : 1
|
||||
|
||||
position.x = max(frame.origin.x, min(position.x, frame.origin.x + frame.size.width - size.width))
|
||||
position.y = max(frame.origin.y, min(position.y, frame.origin.y + frame.size.height - textHeight - 2))
|
||||
|
||||
if position.x + size.width > frame.size.width+frame.origin.x {
|
||||
position.x = point.x - size.width
|
||||
style.alignment = .right
|
||||
@@ -138,7 +143,7 @@ public class LineChartView: NSView {
|
||||
private var stop: Bool = false
|
||||
|
||||
public init(frame: NSRect, num: Int, suffix: String = "%", color: NSColor = .controlAccentColor, scale: Scale = .none, fixedScale: Double = 1, zeroValue: Double = 0.01) {
|
||||
self.points = Array(repeating: nil, count: num)
|
||||
self.points = Array(repeating: nil, count: max(num, 1))
|
||||
self.suffix = suffix
|
||||
self.color = color
|
||||
self.scale = scale
|
||||
@@ -279,49 +284,54 @@ public class LineChartView: NSView {
|
||||
NSAttributedString.init(string: str, attributes: stringAttributes).draw(with: rect)
|
||||
}
|
||||
|
||||
if isTooltipEnabled, let p = self.cursor, let over = list.first(where: { $0.point.x >= p.x }), let under = list.last(where: { $0.point.x <= p.x }) {
|
||||
if isTooltipEnabled, let p = self.cursor, !list.isEmpty {
|
||||
guard p.y <= height else { return }
|
||||
|
||||
let diffOver = over.point.x - p.x
|
||||
let diffUnder = p.x - under.point.x
|
||||
let nearest = (diffOver < diffUnder) ? over : under
|
||||
let vLine = NSBezierPath()
|
||||
let hLine = NSBezierPath()
|
||||
let overPoints = list.filter { $0.point.x >= p.x }
|
||||
let underPoints = list.filter { $0.point.x <= p.x }
|
||||
|
||||
vLine.setLineDash([4, 4], count: 2, phase: 0)
|
||||
hLine.setLineDash([6, 6], count: 2, phase: 0)
|
||||
|
||||
vLine.move(to: CGPoint(x: p.x, y: 0))
|
||||
vLine.line(to: CGPoint(x: p.x, y: height))
|
||||
vLine.close()
|
||||
|
||||
hLine.move(to: CGPoint(x: 0, y: p.y))
|
||||
hLine.line(to: CGPoint(x: self.frame.size.width, y: p.y))
|
||||
hLine.close()
|
||||
|
||||
NSColor.tertiaryLabelColor.set()
|
||||
|
||||
vLine.lineWidth = offset
|
||||
hLine.lineWidth = offset
|
||||
|
||||
vLine.stroke()
|
||||
hLine.stroke()
|
||||
|
||||
let dotSize: CGFloat = 4
|
||||
let path = NSBezierPath(ovalIn: CGRect(
|
||||
x: nearest.point.x-(dotSize/2),
|
||||
y: nearest.point.y-(dotSize/2),
|
||||
width: dotSize,
|
||||
height: dotSize
|
||||
))
|
||||
NSColor.red.set()
|
||||
path.stroke()
|
||||
|
||||
let date = self.dateFormatter.string(from: nearest.value.ts)
|
||||
let roundedValue = (nearest.value.value * 100).rounded(toPlaces: 2)
|
||||
let strValue = roundedValue >= 1 ? "\(Int(roundedValue))\(suffix)" : "\(roundedValue)\(suffix)"
|
||||
let value = toolTipFunc != nil ? toolTipFunc!(nearest.value) : strValue
|
||||
drawToolTip(self.frame, CGPoint(x: nearest.point.x+4, y: nearest.point.y+4), CGSize(width: 78, height: height), value: value, subtitle: date)
|
||||
if let over = overPoints.min(by: { $0.point.x < $1.point.x }), let under = underPoints.max(by: { $0.point.x < $1.point.x }) {
|
||||
let diffOver = over.point.x - p.x
|
||||
let diffUnder = p.x - under.point.x
|
||||
let nearest = (diffOver < diffUnder) ? over : under
|
||||
let vLine = NSBezierPath()
|
||||
let hLine = NSBezierPath()
|
||||
|
||||
vLine.setLineDash([4, 4], count: 2, phase: 0)
|
||||
hLine.setLineDash([6, 6], count: 2, phase: 0)
|
||||
|
||||
vLine.move(to: CGPoint(x: p.x, y: 0))
|
||||
vLine.line(to: CGPoint(x: p.x, y: height))
|
||||
vLine.close()
|
||||
|
||||
hLine.move(to: CGPoint(x: 0, y: p.y))
|
||||
hLine.line(to: CGPoint(x: self.frame.size.width, y: p.y))
|
||||
hLine.close()
|
||||
|
||||
NSColor.tertiaryLabelColor.set()
|
||||
|
||||
vLine.lineWidth = offset
|
||||
hLine.lineWidth = offset
|
||||
|
||||
vLine.stroke()
|
||||
hLine.stroke()
|
||||
|
||||
let dotSize: CGFloat = 4
|
||||
let path = NSBezierPath(ovalIn: CGRect(
|
||||
x: nearest.point.x-(dotSize/2),
|
||||
y: nearest.point.y-(dotSize/2),
|
||||
width: dotSize,
|
||||
height: dotSize
|
||||
))
|
||||
NSColor.red.set()
|
||||
path.stroke()
|
||||
|
||||
let date = self.dateFormatter.string(from: nearest.value.ts)
|
||||
let roundedValue = (nearest.value.value * 100).rounded(toPlaces: 2)
|
||||
let strValue = roundedValue >= 1 ? "\(Int(roundedValue))\(suffix)" : "\(roundedValue)\(suffix)"
|
||||
let value = toolTipFunc != nil ? toolTipFunc!(nearest.value) : strValue
|
||||
drawToolTip(self.frame, CGPoint(x: nearest.point.x+4, y: nearest.point.y+4), CGSize(width: 78, height: height), value: value, subtitle: date)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,6 +351,7 @@ public class LineChartView: NSView {
|
||||
|
||||
public func addValue(_ value: DoubleValue) {
|
||||
self.queue.async(flags: .barrier) {
|
||||
guard !self.points.isEmpty else { return }
|
||||
self.points.remove(at: 0)
|
||||
self.points.append(value)
|
||||
}
|
||||
@@ -421,8 +432,9 @@ public class NetworkChartView: NSView {
|
||||
outColor: NSColor = .systemRed, inColor: NSColor = .systemBlue, scale: Scale = .none, fixedScale: Double = 1) {
|
||||
self.reversedOrder = reversedOrder
|
||||
|
||||
let topFrame = NSRect(x: 0, y: frame.height/2, width: frame.width, height: frame.height/2)
|
||||
let bottomFrame = NSRect(x: 0, y: 0, width: frame.width, height: frame.height/2)
|
||||
let safeHeight = max(frame.height, 2)
|
||||
let topFrame = NSRect(x: frame.origin.x, y: safeHeight/2, width: frame.width, height: safeHeight/2)
|
||||
let bottomFrame = NSRect(x: frame.origin.x, y: 0, width: frame.width, height: safeHeight/2)
|
||||
let inFrame = self.reversedOrder ? topFrame : bottomFrame
|
||||
let outFrame = self.reversedOrder ? bottomFrame : topFrame
|
||||
self.inChart = LineChartView(frame: inFrame, num: num, color: inColor, scale: scale, fixedScale: fixedScale, zeroValue: 256.0)
|
||||
@@ -473,7 +485,8 @@ public class NetworkChartView: NSView {
|
||||
self.inChart.flipY = !self.reversedOrder
|
||||
self.outChart.flipY = self.reversedOrder
|
||||
|
||||
let topFrame = CGPoint(x: 0, y: frame.height/2)
|
||||
let safeHeight = max(frame.height, 2)
|
||||
let topFrame = CGPoint(x: 0, y: safeHeight/2)
|
||||
let bottomFrame = CGPoint(x: 0, y: 0)
|
||||
self.inChart.setFrameOrigin(self.reversedOrder ? topFrame : bottomFrame)
|
||||
self.outChart.setFrameOrigin(self.reversedOrder ? bottomFrame : topFrame)
|
||||
@@ -495,6 +508,17 @@ public class NetworkChartView: NSView {
|
||||
self.inChart.isTooltipEnabled = newState
|
||||
self.outChart.isTooltipEnabled = newState
|
||||
}
|
||||
|
||||
public override func setFrameOrigin(_ newOrigin: NSPoint) {
|
||||
super.setFrameOrigin(newOrigin)
|
||||
|
||||
let safeHeight = max(frame.height, 2)
|
||||
let topFrame = CGPoint(x: 0, y: safeHeight/2)
|
||||
let bottomFrame = CGPoint(x: 0, y: 0)
|
||||
|
||||
self.inChart.setFrameOrigin(self.reversedOrder ? topFrame : bottomFrame)
|
||||
self.outChart.setFrameOrigin(self.reversedOrder ? bottomFrame : topFrame)
|
||||
}
|
||||
}
|
||||
|
||||
public class PieChartView: NSView {
|
||||
@@ -863,10 +887,13 @@ public class BarChartView: NSView {
|
||||
list.append((value: value.value, path: partition))
|
||||
}
|
||||
|
||||
if let p = self.cursor, let block = list.first(where: { $0.path.contains(p) }) {
|
||||
let value = "\(Int(block.value.rounded(toPlaces: 2) * 100))%"
|
||||
let width: CGFloat = block.value == 1 ? 38 : block.value > 0.1 ? 32 : 24
|
||||
drawToolTip(self.frame, CGPoint(x: p.x+4, y: p.y+4), CGSize(width: width, height: partitionSize.height), value: value)
|
||||
if let p = self.cursor {
|
||||
let matchingBlock = list.first(where: { $0.path.contains(p) })
|
||||
if let block = matchingBlock {
|
||||
let value = "\(Int(block.value.rounded(toPlaces: 2) * 100))%"
|
||||
let width: CGFloat = block.value == 1 ? 38 : block.value > 0.1 ? 32 : 24
|
||||
drawToolTip(self.frame, CGPoint(x: p.x+4, y: p.y+4), CGSize(width: width, height: partitionSize.height), value: value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -909,7 +936,8 @@ public class GridChartView: NSView {
|
||||
public init(frame: NSRect, grid: (rows: Int, columns: Int)) {
|
||||
self.grid = grid
|
||||
super.init(frame: frame)
|
||||
self.values = Array(repeating: self.inactiveColor, count: grid.rows * grid.columns)
|
||||
let totalCells = max(grid.rows * grid.columns, 1)
|
||||
self.values = Array(repeating: self.inactiveColor, count: totalCells)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
||||
Reference in New Issue
Block a user