gpt4 book ai didi

swift - Making Pie Chart with Slice space(饼图中每种颜色之间的空间)

转载 作者:搜寻专家 更新时间:2023-11-01 06:59:50 28 4
gpt4 key购买 nike

Pie Chart Sample

我正在做一个项目,我需要在其中实现上面的饼图。我需要在切片之间添加一个空格,如附图所示。需要在带圆弧的切片和带百分比的值中有一个空格。

我已经尝试实现它,但我没能成功。我使用错误的弧形。

请帮帮我。谢谢。

import UIKit

private extension CGFloat {

/// Formats the CGFloat to a maximum of 1 decimal place.

var formattedToOneDecimalPlace : String {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 1
return formatter.string(from: NSNumber(value: self.native)) ?? "\(self)"
}
}

///定义饼图的一部分

struct Segment {

/// The color of the segment
var color : UIColor

/// The name of the segment
var name : String

/// The value of the segment
var value : CGFloat
}

class PieChartView: UIView {

/// An array of structs representing the segments of the pie chart

var segments = [Segment]() {
didSet { setNeedsDisplay() } // re-draw view when the values get set
}

/// Defines whether the segment labels should be shown when drawing the pie chart
var showSegmentLabels = true {
didSet { setNeedsDisplay() }
}

/// Defines whether the segment labels will show the value of the segment in brackets
var showSegmentValueInLabel = false {
didSet { setNeedsDisplay() }
}

/// The font to be used on the segment labels
var segmentLabelFont = UIFont.systemFont(ofSize: 14) {
didSet {
textAttributes[NSAttributedStringKey.font] = segmentLabelFont
setNeedsDisplay()
}
}

private let paragraphStyle : NSParagraphStyle = {
var p = NSMutableParagraphStyle()
p.alignment = .center
return p.copy() as! NSParagraphStyle
}()

private lazy var textAttributes : [NSAttributedStringKey : Any] = {
return [NSAttributedStringKey.paragraphStyle : self.paragraphStyle, NSAttributedStringKey.font : self.segmentLabelFont]
}()

override init(frame: CGRect) {
super.init(frame: frame)
isOpaque = false // when overriding drawRect, you must specify this to maintain transparency.
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

override func draw(_ rect: CGRect) {

// get current context
let ctx = UIGraphicsGetCurrentContext()

// radius is the half the frame's width or height (whichever is smallest)
let radius = min(frame.width, frame.height) * 0.5

// center of the view
let viewCenter = CGPoint(x: bounds.size.width * 0.5, y: bounds.size.height * 0.5)

// enumerate the total value of the segments by using reduce to sum them
let valueCount = segments.reduce(0, {($0 + $1.value)})
// the starting angle is -90 degrees (top of the circle, as the context is flipped). By default, 0 is the right hand side of the circle, with the positive angle being in an anti-clockwise direction (same as a unit circle in maths).
var startAngle = -CGFloat.pi * 0.5

// loop through the values array
for segment in segments {

// set fill color to the segment color
ctx?.setFillColor(segment.color.cgColor)

// update the end angle of the segment
let endAngle = startAngle + .pi * 2 * (segment.value / valueCount)

// move to the center of the pie chart
ctx?.move(to: viewCenter)

// add arc from the center for each segment (anticlockwise is specified for the arc, but as the view flips the context, it will produce a clockwise arc)
ctx?.addArc(center: viewCenter, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false)

// fill segment
ctx?.fillPath()

// ctx?.setStrokeColor(UIColor.white.cgColor)

// ctx?.strokePath()


if showSegmentLabels { // do text rendering

// get the angle midpoint
let halfAngle = startAngle + (endAngle - startAngle) * 0.5;

// the ratio of how far away from the center of the pie chart the text will appear
let textPositionValue : CGFloat = 0.65

// get the 'center' of the segment. It's slightly biased to the outer edge, as it's wider.
let segmentCenter = CGPoint(x: viewCenter.x + radius * textPositionValue * cos(halfAngle), y: viewCenter.y + radius * textPositionValue * sin(halfAngle))

// text to render – the segment value is formatted to 1dp if needed to be displayed.

// Previous
//let textToRender = showSegmentValueInLabel ? "\(segment.name) \(segment.value.formattedToOneDecimalPlace))" : segment.name

// Change
let textToRender = showSegmentValueInLabel ? "\(segment.value.formattedToOneDecimalPlace)%" : segment.name

// get the color components of the segement color
guard let colorComponents = segment.color.cgColor.components else { return }

// get the average brightness of the color
let averageRGB = (colorComponents[0] + colorComponents[1] + colorComponents[2]) / 3

// if too light, use black. If too dark, use white
textAttributes[NSAttributedStringKey.foregroundColor] = (averageRGB > 0.7) ? UIColor.black : UIColor.white

// the bounds that the text will occupy
var renderRect = CGRect(origin: .zero, size: textToRender.size(withAttributes: textAttributes))

// center the origin of the rect
renderRect.origin = CGPoint(x: segmentCenter.x - renderRect.size.width * 0.5, y: segmentCenter.y - renderRect.size.height * 0.5)

// draw text in the rect, with the given attributes
textToRender.draw(in: renderRect, withAttributes: textAttributes)
}

// update starting angle of the next segment to the ending angle of this segment
startAngle = endAngle
}
}
}

最佳答案

在你的draw(_:)中,实现它。

override func draw(_ rect: CGRect) {

let anglePI2 = (CGFloat.pi * 2)
let center = CGPoint.init(x: bounds.size.width / 2, y: bounds.size.height / 2)
let radius = min(bounds.size.width, bounds.size.height) / 2;

let lineWidth: CGFloat = 1;

let ctx = UIGraphicsGetCurrentContext()
ctx?.setLineWidth(lineWidth)


var currentAngle: CGFloat = 0

var totalValue: CGFloat = segments.reduce(0) { $0 + $1.value }
if totalValue <= 0 {
totalValue = 1
}

let iRange = 0 ..< segments.count
for i in iRange {
let segment = segments[i]
// calculate percent
let percent = segment.value / totalValue

let angle = anglePI2 * percent

ctx?.beginPath()
ctx?.move(to: center)
ctx?.addArc(center: center, radius: radius - lineWidth, startAngle: currentAngle, endAngle: currentAngle + angle, clockwise: false)
ctx?.closePath()

ctx?.setFillColor(segment.color.cgColor)
ctx?.fillPath()

ctx?.beginPath()
ctx?.move(to: center)
ctx?.addArc(center: center, radius: radius - (lineWidth / 2), startAngle: currentAngle, endAngle: currentAngle + angle, clockwise: false)
ctx?.closePath()

ctx?.setStrokeColor(UIColor.white.cgColor)
ctx?.strokePath()

currentAngle += angle
}
}

解释
- 计算 pi 等于 360 度。
- 计算中心。
- 计算方 block 的半径。
- 计算[Segment]的totalValue。这在计算切片在圆中的百分比时使用。
- 循环遍历段,计算当前段的百分比,填充圆弧(半径 - 线宽),重新绘制描边路径圆弧(半径 - 线宽/2)。

这是结果 enter image description here

在我的 View Controller 中,我添加了数据(供您引用)。像这样

scv.segments = [
Segment.init(color: UIColor.init(red: 0xfe/0xff, green: 0x4a/0xff, blue: 0x49/0xff, alpha: 1), value: 20),
Segment.init(color: UIColor.init(red: 0x2a/0xff, green: 0xb7/0xff, blue: 0xca/0xff, alpha: 1), value: 15),
Segment.init(color: UIColor.init(red: 0xfe/0xff, green: 0xd7/0xff, blue: 0x66/0xff, alpha: 1), value: 15),
Segment.init(color: UIColor.init(red: 0x4a/0xff, green: 0x4e/0xff, blue: 0x4d/0xff, alpha: 1), value: 50)
]

已更新
这是您的问题和要求的附加要求的完整代码。

struct Segment {

// the color of a given segment
var color: UIColor

// the value of a given segment – will be used to automatically calculate a ratio
var value: CGFloat
}

class SliceView: UIView {

/// An array of structs representing the segments of the pie chart
var segments = [Segment]() {
didSet {
totalValue = segments.reduce(0) { $0 + $1.value }
setupLabels()
setNeedsDisplay() // re-draw view when the values get set
layoutLabels();
}
}

private var totalValue: CGFloat = 1;
private var labels: [UILabel] = []




override init(frame: CGRect) {
super.init(frame: frame)
isOpaque = false // when overriding drawRect, you must specify this to maintain transparency.
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

override func draw(_ rect: CGRect) {

let anglePI2 = (CGFloat.pi * 2)
let center = CGPoint.init(x: bounds.size.width / 2, y: bounds.size.height / 2)
let radius = min(bounds.size.width, bounds.size.height) / 2;

let lineWidth: CGFloat = 1;

let ctx = UIGraphicsGetCurrentContext()
ctx?.setLineWidth(lineWidth)


var currentAngle: CGFloat = 0

if totalValue <= 0 {
totalValue = 1
}

let iRange = 0 ..< segments.count
for i in iRange {
let segment = segments[i]
// calculate percent
let percent = segment.value / totalValue

let angle = anglePI2 * percent

ctx?.beginPath()
ctx?.move(to: center)
ctx?.addArc(center: center, radius: radius - lineWidth, startAngle: currentAngle, endAngle: currentAngle + angle, clockwise: false)
ctx?.closePath()

ctx?.setFillColor(segment.color.cgColor)
ctx?.fillPath()

ctx?.beginPath()
ctx?.move(to: center)
ctx?.addArc(center: center, radius: radius - (lineWidth / 2), startAngle: currentAngle, endAngle: currentAngle + angle, clockwise: false)
ctx?.closePath()

ctx?.setStrokeColor(UIColor.white.cgColor)
ctx?.strokePath()

currentAngle += angle
}


}

override func layoutSubviews() {
super.layoutSubviews()
self.layoutLabels()
}
private func setupLabels() {
var diff = segments.count - labels.count;
if diff >= 0 {
for _ in 0 ..< diff {
let lbl = UILabel()
self.addSubview(lbl)
labels.append(lbl)
}
} else { // diff < 0 (minus values)
// loop until diff is 0
//
while diff != 0 {
var lbl: UILabel!
// if there is no more labels to remove
// break the loop
if labels.count <= 0 {
break;
}
// get the last label
lbl = labels.removeLast()
if lbl.superview != nil {
lbl.removeFromSuperview()
}
// increment the minus value by 1
diff += 1;
}
}

for i in 0 ..< segments.count {
let lbl = labels[i]
lbl.textColor = UIColor.white
// Change here for your text display
// I currently display percent of each pies
lbl.text = String.init(format: "%0.1f", segments[i].value)
lbl.font = UIFont.init(name: "ArialRoundedMT-Bold", size: 12)
}
}
func layoutLabels() {
let anglePI2 = CGFloat.pi * 2
let center = CGPoint.init(x: bounds.size.width / 2, y: bounds.size.height / 2)
let radius = min(bounds.size.width / 2, bounds.size.height / 2) / 2

var currentAngle: CGFloat = 0;
let iRange = 0 ..< labels.count
for i in iRange {
let lbl = labels[i]
let percent = segments[i].value / totalValue

let intervalAngle = anglePI2 * percent;
lbl.frame = .zero;
lbl.sizeToFit()

let x = center.x + radius * cos(currentAngle + (intervalAngle / 2))
let y = center.y + radius * sin(currentAngle + (intervalAngle / 2))
lbl.center = CGPoint.init(x: x, y: y)

currentAngle += intervalAngle

}
}

}

结果是
enter image description here

关于swift - Making Pie Chart with Slice space(饼图中每种颜色之间的空间),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51438669/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com