mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
Storage improvements
This commit is contained in:
@@ -13,15 +13,88 @@ import MultilineTextComponent
|
||||
import EmojiStatusComponent
|
||||
import Postbox
|
||||
|
||||
private func interpolateChartData(start: PieChartComponent.ChartData, end: PieChartComponent.ChartData, progress: CGFloat) -> PieChartComponent.ChartData {
|
||||
if start.items.count != end.items.count {
|
||||
return start
|
||||
}
|
||||
|
||||
var result = end
|
||||
for i in 0 ..< result.items.count {
|
||||
result.items[i].value = (1.0 - progress) * start.items[i].value + progress * end.items[i].value
|
||||
result.items[i].color = start.items[i].color.interpolateTo(end.items[i].color, fraction: progress) ?? end.items[i].color
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private func processChartData(data: PieChartComponent.ChartData) -> PieChartComponent.ChartData {
|
||||
var data = data
|
||||
|
||||
let minValue: Double = 0.01
|
||||
|
||||
var totalSum: CGFloat = 0.0
|
||||
for i in 0 ..< data.items.count {
|
||||
if data.items[i].value > 0.00001 {
|
||||
data.items[i].value = max(data.items[i].value, minValue)
|
||||
}
|
||||
totalSum += data.items[i].value
|
||||
}
|
||||
|
||||
var hasOneItem = false
|
||||
for i in 0 ..< data.items.count {
|
||||
if data.items[i].value != 0 && totalSum == data.items[i].value {
|
||||
data.items[i].value = 1.0
|
||||
hasOneItem = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasOneItem {
|
||||
if abs(totalSum - 1.0) > 0.0001 {
|
||||
let deltaValue = totalSum - 1.0
|
||||
|
||||
var availableSum: Double = 0.0
|
||||
for i in 0 ..< data.items.count {
|
||||
let itemValue = data.items[i].value
|
||||
let availableItemValue = max(0.0, itemValue - minValue)
|
||||
if availableItemValue > 0.0 {
|
||||
availableSum += availableItemValue
|
||||
}
|
||||
}
|
||||
totalSum = 0.0
|
||||
let itemFraction = deltaValue / availableSum
|
||||
for i in 0 ..< data.items.count {
|
||||
let itemValue = data.items[i].value
|
||||
let availableItemValue = max(0.0, itemValue - minValue)
|
||||
if availableItemValue > 0.0 {
|
||||
let itemDelta = availableItemValue * itemFraction
|
||||
data.items[i].value -= itemDelta
|
||||
}
|
||||
totalSum += data.items[i].value
|
||||
}
|
||||
}
|
||||
|
||||
if totalSum > 0.0 && totalSum < 1.0 - 0.0001 {
|
||||
for i in 0 ..< data.items.count {
|
||||
data.items[i].value /= totalSum
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
final class PieChartComponent: Component {
|
||||
struct ChartData: Equatable {
|
||||
struct Item: Equatable {
|
||||
var id: AnyHashable
|
||||
var displayValue: Double
|
||||
var value: Double
|
||||
var color: UIColor
|
||||
|
||||
init(id: AnyHashable, value: Double, color: UIColor) {
|
||||
init(id: AnyHashable, displayValue: Double, value: Double, color: UIColor) {
|
||||
self.id = id
|
||||
self.displayValue = displayValue
|
||||
self.value = value
|
||||
self.color = color
|
||||
}
|
||||
@@ -55,96 +128,98 @@ final class PieChartComponent: Component {
|
||||
return true
|
||||
}
|
||||
|
||||
class View: UIView {
|
||||
private var shapeLayers: [AnyHashable: SimpleShapeLayer] = [:]
|
||||
private var labels: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
var selectedKey: AnyHashable?
|
||||
private final class ChartDataView: UIView {
|
||||
private(set) var theme: PresentationTheme?
|
||||
private(set) var data: ChartData?
|
||||
private(set) var selectedKey: AnyHashable?
|
||||
|
||||
private weak var state: EmptyComponentState?
|
||||
private var currentAnimation: (start: ChartData, end: ChartData, current: ChartData, progress: CGFloat)?
|
||||
private var animator: DisplayLinkAnimator?
|
||||
|
||||
private var labels: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
self.backgroundColor = nil
|
||||
self.isOpaque = false
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
let point = recognizer.location(in: self)
|
||||
for (key, layer) in self.shapeLayers {
|
||||
if layer.frame.contains(point), let path = layer.path {
|
||||
if path.contains(self.layer.convert(point, to: layer)) {
|
||||
if self.selectedKey == key {
|
||||
self.selectedKey = nil
|
||||
} else {
|
||||
self.selectedKey = key
|
||||
}
|
||||
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||
|
||||
break
|
||||
}
|
||||
deinit {
|
||||
self.animator?.invalidate()
|
||||
}
|
||||
|
||||
func setItems(theme: PresentationTheme, data: ChartData, selectedKey: AnyHashable?, animated: Bool) {
|
||||
let data = processChartData(data: data)
|
||||
|
||||
if self.theme !== theme || self.data != data || self.selectedKey != selectedKey {
|
||||
self.theme = theme
|
||||
self.selectedKey = selectedKey
|
||||
|
||||
if animated, let previous = self.data {
|
||||
var initialState = previous
|
||||
if let currentAnimation = self.currentAnimation {
|
||||
initialState = currentAnimation.current
|
||||
}
|
||||
self.currentAnimation = (initialState, data, initialState, 0.0)
|
||||
self.animator?.invalidate()
|
||||
self.animator = DisplayLinkAnimator(duration: 0.4, from: 0.0, to: 1.0, update: { [weak self] progress in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let progress = listViewAnimationCurveSystem(progress)
|
||||
if let currentAnimationValue = self.currentAnimation {
|
||||
self.currentAnimation = (currentAnimationValue.start, currentAnimationValue.end, interpolateChartData(start: currentAnimationValue.start, end: currentAnimationValue.end, progress: progress), progress)
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}, completion: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.currentAnimation = nil
|
||||
self.setNeedsDisplay()
|
||||
})
|
||||
}
|
||||
|
||||
self.data = data
|
||||
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: PieChartComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.state = state
|
||||
override func draw(_ rect: CGRect) {
|
||||
guard let context = UIGraphicsGetCurrentContext() else {
|
||||
return
|
||||
}
|
||||
guard let theme = self.theme, let data = self.currentAnimation?.current ?? self.data else {
|
||||
return
|
||||
}
|
||||
if data.items.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
let innerDiameter: CGFloat = 100.0
|
||||
let spacing: CGFloat = 2.0
|
||||
let innerAngleSpacing: CGFloat = spacing / (innerDiameter * 0.5)
|
||||
let minAngle: CGFloat = innerAngleSpacing * 2.0 + 2.0 / (innerDiameter * 0.5)
|
||||
//let minAngle: CGFloat = innerAngleSpacing * 2.0 + 2.0 / (innerDiameter * 0.5)
|
||||
|
||||
var valueSum: Double = 0.0
|
||||
for item in component.chartData.items {
|
||||
valueSum += item.value
|
||||
}
|
||||
var angles: [Double] = []
|
||||
var totalAngle: Double = 0.0
|
||||
for i in 0 ..< component.chartData.items.count {
|
||||
let item = component.chartData.items[i]
|
||||
var angle = item.value / valueSum * CGFloat.pi * 2.0
|
||||
if angle > .ulpOfOne {
|
||||
if angle < minAngle {
|
||||
angle = minAngle
|
||||
}
|
||||
totalAngle += angle
|
||||
}
|
||||
for i in 0 ..< data.items.count {
|
||||
let item = data.items[i]
|
||||
let angle = item.value * CGFloat.pi * 2.0
|
||||
angles.append(angle)
|
||||
}
|
||||
if totalAngle > CGFloat.pi * 2.0 {
|
||||
let deltaAngle = totalAngle - CGFloat.pi * 2.0
|
||||
|
||||
var availableAngleSum: Double = 0.0
|
||||
for i in 0 ..< angles.count {
|
||||
let itemAngle = angles[i]
|
||||
let availableItemAngle = max(0.0, itemAngle - minAngle)
|
||||
if availableItemAngle > 0.0 {
|
||||
availableAngleSum += availableItemAngle
|
||||
}
|
||||
}
|
||||
let itemFraction = deltaAngle / availableAngleSum
|
||||
for i in 0 ..< angles.count {
|
||||
let availableItemAngle = max(0.0, angles[i] - minAngle)
|
||||
if availableItemAngle > 0.0 {
|
||||
let itemDelta = availableItemAngle * itemFraction
|
||||
angles[i] -= itemDelta
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let diameter: CGFloat = 200.0
|
||||
let reducedDiameter: CGFloat = 170.0
|
||||
|
||||
var startAngle: CGFloat = 0.0
|
||||
for i in 0 ..< component.chartData.items.count {
|
||||
let item = component.chartData.items[i]
|
||||
for i in 0 ..< data.items.count {
|
||||
let item = data.items[i]
|
||||
|
||||
let itemOuterDiameter: CGFloat
|
||||
if let selectedKey = self.selectedKey {
|
||||
@@ -157,21 +232,10 @@ final class PieChartComponent: Component {
|
||||
itemOuterDiameter = diameter
|
||||
}
|
||||
|
||||
let shapeLayerFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - diameter) / 2.0), y: 0.0), size: CGSize(width: diameter, height: diameter))
|
||||
let shapeLayerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: diameter, height: diameter))
|
||||
|
||||
let angleSpacing: CGFloat = spacing / (itemOuterDiameter * 0.5)
|
||||
|
||||
let shapeLayer: SimpleShapeLayer
|
||||
if let current = self.shapeLayers[item.id] {
|
||||
shapeLayer = current
|
||||
} else {
|
||||
shapeLayer = SimpleShapeLayer()
|
||||
self.shapeLayers[item.id] = shapeLayer
|
||||
self.layer.insertSublayer(shapeLayer, at: 0)
|
||||
}
|
||||
|
||||
transition.setFrame(layer: shapeLayer, frame: shapeLayerFrame)
|
||||
|
||||
let angleValue: CGFloat = angles[i]
|
||||
|
||||
let innerStartAngle = startAngle + innerAngleSpacing * 0.5
|
||||
@@ -187,14 +251,17 @@ final class PieChartComponent: Component {
|
||||
path.addArc(center: CGPoint(x: diameter * 0.5, y: diameter * 0.5), radius: innerDiameter * 0.5, startAngle: innerEndAngle, endAngle: innerStartAngle, clockwise: true)
|
||||
path.addArc(center: CGPoint(x: diameter * 0.5, y: diameter * 0.5), radius: itemOuterDiameter * 0.5, startAngle: outerStartAngle, endAngle: outerEndAngle, clockwise: false)
|
||||
|
||||
transition.setShapeLayerPath(layer: shapeLayer, path: path)
|
||||
context.addPath(path)
|
||||
context.setFillColor(item.color.cgColor)
|
||||
context.fillPath()
|
||||
|
||||
startAngle += angleValue
|
||||
shapeLayer.fillColor = item.color.cgColor
|
||||
|
||||
let fractionValue: Double = floor(item.value * 100.0 * 10.0) / 10.0
|
||||
let fractionValue: Double = floor(item.displayValue * 100.0 * 10.0) / 10.0
|
||||
let fractionString: String
|
||||
if abs(Double(Int(fractionValue)) - fractionValue) < 0.001 {
|
||||
if fractionValue < 0.1 {
|
||||
fractionString = "<0.1"
|
||||
} else if abs(Double(Int(fractionValue)) - fractionValue) < 0.001 {
|
||||
fractionString = "\(Int(fractionValue))"
|
||||
} else {
|
||||
fractionString = "\(fractionValue)"
|
||||
@@ -207,16 +274,125 @@ final class PieChartComponent: Component {
|
||||
label = ComponentView<Empty>()
|
||||
self.labels[item.id] = label
|
||||
}
|
||||
let labelSize = label.update(transition: .immediate, component: AnyComponent(Text(text: "\(fractionString)%", font: Font.with(size: 16.0, design: .round, weight: .semibold), color: component.theme.list.itemCheckColors.foregroundColor)), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0))
|
||||
let labelSize = label.update(transition: .immediate, component: AnyComponent(Text(text: "\(fractionString)%", font: Font.with(size: 16.0, design: .round, weight: .semibold), color: theme.list.itemCheckColors.foregroundColor)), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0))
|
||||
|
||||
var labelFrame: CGRect?
|
||||
|
||||
for step in 0 ... 6 {
|
||||
let stepFraction: CGFloat = CGFloat(step) / 6.0
|
||||
let centerOffset: CGFloat = 0.5 * (1.0 - stepFraction) + 0.65 * stepFraction
|
||||
|
||||
if angleValue >= 0.001 {
|
||||
for step in 0 ... 20 {
|
||||
let stepFraction: CGFloat = CGFloat(step) / 20.0
|
||||
let centerOffset: CGFloat = 0.5 * (1.0 - stepFraction) + 0.65 * stepFraction
|
||||
|
||||
let midAngle: CGFloat = (innerStartAngle + innerEndAngle) * 0.5
|
||||
let centerDistance: CGFloat = (innerDiameter * 0.5 + (diameter * 0.5 - innerDiameter * 0.5) * centerOffset)
|
||||
|
||||
let relLabelCenter = CGPoint(
|
||||
x: cos(midAngle) * centerDistance,
|
||||
y: sin(midAngle) * centerDistance
|
||||
)
|
||||
|
||||
let labelCenter = CGPoint(
|
||||
x: shapeLayerFrame.midX + relLabelCenter.x,
|
||||
y: shapeLayerFrame.midY + relLabelCenter.y
|
||||
)
|
||||
|
||||
func lineCircleIntersection(_ center: CGPoint, _ p1: CGPoint, _ p2: CGPoint, _ r: CGFloat) -> CGFloat {
|
||||
let dx: CGFloat = p2.x - p1.x
|
||||
let dy: CGFloat = p2.y - p1.y
|
||||
let dr: CGFloat = sqrt(dx * dx + dy * dy)
|
||||
let D: CGFloat = p1.x * p2.y - p2.x * p1.y
|
||||
|
||||
var minDistance: CGFloat = 10000.0
|
||||
|
||||
for i in 0 ..< 2 {
|
||||
let signFactor: CGFloat = i == 0 ? 1.0 : (-1.0)
|
||||
let dysign: CGFloat = dy < 0.0 ? -1.0 : 1.0
|
||||
let ix: CGFloat = (D * dy + signFactor * dysign * dx * sqrt(r * r * dr * dr - D * D)) / (dr * dr)
|
||||
let iy: CGFloat = (-D * dx + signFactor * abs(dy) * sqrt(r * r * dr * dr - D * D)) / (dr * dr)
|
||||
let distance: CGFloat = sqrt(pow(ix - center.x, 2.0) + pow(iy - center.y, 2.0))
|
||||
minDistance = min(minDistance, distance)
|
||||
}
|
||||
|
||||
return minDistance
|
||||
}
|
||||
|
||||
func lineLineIntersection(_ p1: CGPoint, _ p2: CGPoint, _ p3: CGPoint, _ p4: CGPoint) -> CGFloat {
|
||||
let x1 = p1.x
|
||||
let y1 = p1.y
|
||||
let x2 = p2.x
|
||||
let y2 = p2.y
|
||||
let x3 = p3.x
|
||||
let y3 = p3.y
|
||||
let x4 = p4.x
|
||||
let y4 = p4.y
|
||||
|
||||
let d: CGFloat = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
|
||||
if abs(d) <= 0.00001 {
|
||||
return 10000.0
|
||||
}
|
||||
|
||||
let px: CGFloat = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d
|
||||
let py: CGFloat = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d
|
||||
|
||||
let distance: CGFloat = sqrt(pow(px - p1.x, 2.0) + pow(py - p1.y, 2.0))
|
||||
return distance
|
||||
}
|
||||
|
||||
let intersectionOuterTopRight = lineCircleIntersection(relLabelCenter, relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y + labelSize.height * 0.5), diameter * 0.5)
|
||||
let intersectionInnerTopRight = lineCircleIntersection(relLabelCenter, relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y + labelSize.height * 0.5), innerDiameter * 0.5)
|
||||
let intersectionOuterBottomRight = lineCircleIntersection(relLabelCenter, relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y - labelSize.height * 0.5), diameter * 0.5)
|
||||
let intersectionInnerBottomRight = lineCircleIntersection(relLabelCenter, relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y - labelSize.height * 0.5), innerDiameter * 0.5)
|
||||
|
||||
let intersectionLine1TopRight = lineLineIntersection(relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y + labelSize.height * 0.5), CGPoint(), CGPoint(x: cos(innerStartAngle), y: sin(innerStartAngle)))
|
||||
let intersectionLine1BottomRight = lineLineIntersection(relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y - labelSize.height * 0.5), CGPoint(), CGPoint(x: cos(innerStartAngle), y: sin(innerStartAngle)))
|
||||
let intersectionLine2TopRight = lineLineIntersection(relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y + labelSize.height * 0.5), CGPoint(), CGPoint(x: cos(innerEndAngle), y: sin(innerEndAngle)))
|
||||
let intersectionLine2BottomRight = lineLineIntersection(relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y - labelSize.height * 0.5), CGPoint(), CGPoint(x: cos(innerEndAngle), y: sin(innerEndAngle)))
|
||||
|
||||
var distances: [CGFloat] = [
|
||||
intersectionOuterTopRight,
|
||||
intersectionInnerTopRight,
|
||||
intersectionOuterBottomRight,
|
||||
intersectionInnerBottomRight
|
||||
]
|
||||
|
||||
if angleValue < CGFloat.pi / 2.0 {
|
||||
distances.append(contentsOf: [
|
||||
intersectionLine1TopRight,
|
||||
intersectionLine1BottomRight,
|
||||
intersectionLine2TopRight,
|
||||
intersectionLine2BottomRight
|
||||
] as [CGFloat])
|
||||
}
|
||||
|
||||
var minDistance: CGFloat = 1000.0
|
||||
for distance in distances {
|
||||
minDistance = min(minDistance, distance + 1.0)
|
||||
}
|
||||
|
||||
let diagonalAngle = atan2(labelSize.height, labelSize.width)
|
||||
|
||||
let maxHalfWidth = cos(diagonalAngle) * minDistance
|
||||
let maxHalfHeight = sin(diagonalAngle) * minDistance
|
||||
|
||||
let maxSize = CGSize(width: maxHalfWidth * 2.0, height: maxHalfHeight * 2.0)
|
||||
let finalSize = CGSize(width: min(labelSize.width, maxSize.width), height: min(labelSize.height, maxSize.height))
|
||||
|
||||
let currentFrame = CGRect(origin: CGPoint(x: labelCenter.x - finalSize.width * 0.5, y: labelCenter.y - finalSize.height * 0.5), size: finalSize)
|
||||
|
||||
if finalSize.width >= labelSize.width {
|
||||
labelFrame = currentFrame
|
||||
break
|
||||
}
|
||||
if let labelFrame {
|
||||
if labelFrame.width > finalSize.width {
|
||||
continue
|
||||
}
|
||||
}
|
||||
labelFrame = currentFrame
|
||||
}
|
||||
} else {
|
||||
let midAngle: CGFloat = (innerStartAngle + innerEndAngle) * 0.5
|
||||
let centerDistance: CGFloat = (innerDiameter * 0.5 + (diameter * 0.5 - innerDiameter * 0.5) * centerOffset)
|
||||
let centerDistance: CGFloat = (innerDiameter * 0.5 + (diameter * 0.5 - innerDiameter * 0.5) * 0.5)
|
||||
|
||||
let relLabelCenter = CGPoint(
|
||||
x: cos(midAngle) * centerDistance,
|
||||
@@ -228,103 +404,14 @@ final class PieChartComponent: Component {
|
||||
y: shapeLayerFrame.midY + relLabelCenter.y
|
||||
)
|
||||
|
||||
func lineCircleIntersection(_ center: CGPoint, _ p1: CGPoint, _ p2: CGPoint, _ r: CGFloat) -> CGFloat {
|
||||
let dx: CGFloat = p2.x - p1.x
|
||||
let dy: CGFloat = p2.y - p1.y
|
||||
let dr: CGFloat = sqrt(dx * dx + dy * dy)
|
||||
let D: CGFloat = p1.x * p2.y - p2.x * p1.y
|
||||
|
||||
var minDistance: CGFloat = 10000.0
|
||||
|
||||
for i in 0 ..< 2 {
|
||||
let signFactor: CGFloat = i == 0 ? 1.0 : (-1.0)
|
||||
let dysign: CGFloat = dy < 0.0 ? -1.0 : 1.0
|
||||
let ix: CGFloat = (D * dy + signFactor * dysign * dx * sqrt(r * r * dr * dr - D * D)) / (dr * dr)
|
||||
let iy: CGFloat = (-D * dx + signFactor * abs(dy) * sqrt(r * r * dr * dr - D * D)) / (dr * dr)
|
||||
let distance: CGFloat = sqrt(pow(ix - center.x, 2.0) + pow(iy - center.y, 2.0))
|
||||
minDistance = min(minDistance, distance)
|
||||
}
|
||||
|
||||
return minDistance
|
||||
}
|
||||
|
||||
func lineLineIntersection(_ p1: CGPoint, _ p2: CGPoint, _ p3: CGPoint, _ p4: CGPoint) -> CGFloat {
|
||||
let x1 = p1.x
|
||||
let y1 = p1.y
|
||||
let x2 = p2.x
|
||||
let y2 = p2.y
|
||||
let x3 = p3.x
|
||||
let y3 = p3.y
|
||||
let x4 = p4.x
|
||||
let y4 = p4.y
|
||||
|
||||
let d: CGFloat = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
|
||||
if abs(d) <= 0.00001 {
|
||||
return 10000.0
|
||||
}
|
||||
|
||||
let px: CGFloat = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d
|
||||
let py: CGFloat = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d
|
||||
|
||||
let distance: CGFloat = sqrt(pow(px - p1.x, 2.0) + pow(py - p1.y, 2.0))
|
||||
return distance
|
||||
}
|
||||
|
||||
let intersectionOuterTopRight = lineCircleIntersection(relLabelCenter, relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y + labelSize.height * 0.5), diameter * 0.5)
|
||||
let intersectionInnerTopRight = lineCircleIntersection(relLabelCenter, relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y + labelSize.height * 0.5), innerDiameter * 0.5)
|
||||
let intersectionOuterBottomRight = lineCircleIntersection(relLabelCenter, relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y - labelSize.height * 0.5), diameter * 0.5)
|
||||
let intersectionInnerBottomRight = lineCircleIntersection(relLabelCenter, relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y - labelSize.height * 0.5), innerDiameter * 0.5)
|
||||
|
||||
let intersectionLine1TopRight = lineLineIntersection(relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y + labelSize.height * 0.5), CGPoint(), CGPoint(x: cos(innerStartAngle), y: sin(innerStartAngle)))
|
||||
let intersectionLine1BottomRight = lineLineIntersection(relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y - labelSize.height * 0.5), CGPoint(), CGPoint(x: cos(innerStartAngle), y: sin(innerStartAngle)))
|
||||
let intersectionLine2TopRight = lineLineIntersection(relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y + labelSize.height * 0.5), CGPoint(), CGPoint(x: cos(innerEndAngle), y: sin(innerEndAngle)))
|
||||
let intersectionLine2BottomRight = lineLineIntersection(relLabelCenter, CGPoint(x: relLabelCenter.x + labelSize.width * 0.5, y: relLabelCenter.y - labelSize.height * 0.5), CGPoint(), CGPoint(x: cos(innerEndAngle), y: sin(innerEndAngle)))
|
||||
|
||||
var distances: [CGFloat] = [
|
||||
intersectionOuterTopRight,
|
||||
intersectionInnerTopRight,
|
||||
intersectionOuterBottomRight,
|
||||
intersectionInnerBottomRight
|
||||
]
|
||||
|
||||
if angleValue < CGFloat.pi / 2.0 {
|
||||
distances.append(contentsOf: [
|
||||
intersectionLine1TopRight,
|
||||
intersectionLine1BottomRight,
|
||||
intersectionLine2TopRight,
|
||||
intersectionLine2BottomRight
|
||||
] as [CGFloat])
|
||||
}
|
||||
|
||||
var minDistance: CGFloat = 1000.0
|
||||
for distance in distances {
|
||||
minDistance = min(minDistance, distance + 1.0)
|
||||
}
|
||||
|
||||
let diagonalAngle = atan2(labelSize.height, labelSize.width)
|
||||
|
||||
let maxHalfWidth = cos(diagonalAngle) * minDistance
|
||||
let maxHalfHeight = sin(diagonalAngle) * minDistance
|
||||
|
||||
let maxSize = CGSize(width: maxHalfWidth * 2.0, height: maxHalfHeight * 2.0)
|
||||
let finalSize = CGSize(width: min(labelSize.width, maxSize.width), height: min(labelSize.height, maxSize.height))
|
||||
|
||||
let currentFrame = CGRect(origin: CGPoint(x: labelCenter.x - finalSize.width * 0.5, y: labelCenter.y - finalSize.height * 0.5), size: finalSize)
|
||||
|
||||
if finalSize.width >= labelSize.width {
|
||||
labelFrame = currentFrame
|
||||
break
|
||||
}
|
||||
if let labelFrame {
|
||||
if labelFrame.width > finalSize.width {
|
||||
continue
|
||||
}
|
||||
}
|
||||
labelFrame = currentFrame
|
||||
let minSize = labelSize.aspectFitted(CGSize(width: 4.0, height: 4.0))
|
||||
labelFrame = CGRect(origin: CGPoint(x: labelCenter.x - minSize.width * 0.5, y: labelCenter.y - minSize.height * 0.5), size: minSize)
|
||||
}
|
||||
|
||||
if let labelView = label.view, let labelFrame {
|
||||
var animateIn: Bool = false
|
||||
if labelView.superview == nil {
|
||||
animateIn = true
|
||||
self.addSubview(labelView)
|
||||
}
|
||||
|
||||
@@ -338,11 +425,12 @@ final class PieChartComponent: Component {
|
||||
y: labelFrame.midY - shapeLayerFrame.midY
|
||||
)
|
||||
|
||||
let labelAlpha: CGFloat
|
||||
if let selectedKey = self.selectedKey {
|
||||
if selectedKey == item.id {
|
||||
transition.setAlpha(view: labelView, alpha: normalAlpha)
|
||||
labelAlpha = normalAlpha
|
||||
} else {
|
||||
transition.setAlpha(view: labelView, alpha: 0.0)
|
||||
labelAlpha = 0.0
|
||||
|
||||
let reducedFactor: CGFloat = (reducedDiameter - innerDiameter) / (diameter - innerDiameter)
|
||||
let reducedDiameterFactor: CGFloat = reducedDiameter / diameter
|
||||
@@ -353,7 +441,16 @@ final class PieChartComponent: Component {
|
||||
relLabelCenter.y *= reducedDiameterFactor
|
||||
}
|
||||
} else {
|
||||
transition.setAlpha(view: labelView, alpha: normalAlpha)
|
||||
labelAlpha = normalAlpha
|
||||
}
|
||||
if labelView.alpha != labelAlpha {
|
||||
let transition: Transition
|
||||
if animateIn {
|
||||
transition = .immediate
|
||||
} else {
|
||||
transition = Transition(animation: .curve(duration: 0.18, curve: .easeInOut))
|
||||
}
|
||||
transition.setAlpha(view: labelView, alpha: labelAlpha)
|
||||
}
|
||||
|
||||
let labelCenter = CGPoint(
|
||||
@@ -361,10 +458,61 @@ final class PieChartComponent: Component {
|
||||
y: shapeLayerFrame.midY + relLabelCenter.y
|
||||
)
|
||||
|
||||
transition.setPosition(view: labelView, position: labelCenter)
|
||||
transition.setScale(view: labelView, scale: labelScale)
|
||||
labelView.center = labelCenter
|
||||
labelView.transform = CGAffineTransformMakeScale(labelScale, labelScale)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class View: UIView {
|
||||
private let dataView: ChartDataView
|
||||
private var labels: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
var selectedKey: AnyHashable?
|
||||
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.dataView = ChartDataView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.dataView)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
let point = recognizer.location(in: self)
|
||||
let _ = point
|
||||
/*for (key, layer) in self.shapeLayers {
|
||||
if layer.frame.contains(point), let path = layer.path {
|
||||
if path.contains(self.layer.convert(point, to: layer)) {
|
||||
if self.selectedKey == key {
|
||||
self.selectedKey = nil
|
||||
} else {
|
||||
self.selectedKey = key
|
||||
}
|
||||
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: PieChartComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.state = state
|
||||
|
||||
transition.setFrame(view: self.dataView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - 200.0) / 2.0), y: 0.0), size: CGSize(width: 200.0, height: 200.0)))
|
||||
self.dataView.setItems(theme: component.theme, data: component.chartData, selectedKey: self.selectedKey, animated: !transition.animation.isImmediate)
|
||||
|
||||
return CGSize(width: availableSize.width, height: 200.0)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user