【问题标题】:Pie Chart slices in swift饼图切片快速
【发布时间】:2015-05-15 18:38:31
【问题描述】:

我正在尝试制作饼图。实际上已经完成了,但是我想获取一些值,并且每个值都应该是馅饼的一部分。我唯一能做的就是用滑块填充馅饼。如何为某些值制作具有不同颜色的不同切片?

这是我绘制图表的代码(我在堆栈中):

   import UIKit

@IBDesignable class ChartView: UIView {

    @IBInspectable var progress : Double =  0.0 {

        didSet {
                self.setNeedsDisplay()
            }
        }


    @IBInspectable var noProgress : Double =  0.0 {

        didSet {
            self.setNeedsDisplay()
        }
    }


        required init(coder aDecoder: NSCoder) {
            super.init(coder:aDecoder)
            self.contentMode = .Redraw
        }

        override init(frame: CGRect) {
            super.init(frame: frame)
            self.backgroundColor = UIColor.clearColor()
            self.contentMode = .Redraw
        }

        override func drawRect(rect: CGRect) {
            let color = UIColor.blueColor().CGColor
            let lineWidth : CGFloat = 2.0

            // Calculate box with insets
            let margin: CGFloat = lineWidth
            let box0 = CGRectInset(self.bounds, margin, margin)
            let side : CGFloat = min(box0.width, box0.height)
            let box = CGRectMake((self.bounds.width-side)/2, (self.bounds.height-side)/2,side,side)


            let ctx = UIGraphicsGetCurrentContext()

            // Draw outline
            CGContextBeginPath(ctx)
            CGContextSetStrokeColorWithColor(ctx, UIColor.blackColor().CGColor)
            CGContextSetLineWidth(ctx, lineWidth)
            CGContextAddEllipseInRect(ctx, box)
            CGContextClosePath(ctx)
            CGContextStrokePath(ctx)

            // Draw arc
            let delta : CGFloat = -CGFloat(M_PI_2)
            let radius : CGFloat = min(box.width, box.height)/2.0

            func prog_to_rad(p: Double) -> CGFloat {
                let rad = CGFloat((p *  M_PI)/180)
                return rad
            }

            func draw_arc(s: CGFloat, e: CGFloat, color: CGColor) {
                CGContextBeginPath(ctx)
                CGContextMoveToPoint(ctx, box.midX, box.midY)
                CGContextSetFillColorWithColor(ctx, color)

                CGContextAddArc(ctx, box.midX, box.midY, radius-lineWidth/2, s, e, 0)

                CGContextClosePath(ctx)
                CGContextFillPath(ctx)
            }


            if progress > 0 {
                let s = prog_to_rad(noProgress * 360/100)
                let e = prog_to_rad(progress * 360/100)
                draw_arc(s, e, color)
            }


    }
}

这是我的 ViewController:

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var pieChartView: ChartView!
    @IBOutlet weak var slider: UISlider!



    override func viewDidLoad() {
        super.viewDidLoad()
   // Do any additional setup after loading the view, typically from a nib.



    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }



    @IBAction func setValue(sender: UISlider) {

    pieChartView.progress = Double(sender.value)



    }


}

【问题讨论】:

    标签: ios swift charts


    【解决方案1】:

    此代码来自我的blogpost,它使用 CAShapeLayer 和 UIBezierPath。您可以使用您喜欢的任何颜色创建任意数量的分段。

    extension CGFloat {
        func radians() -> CGFloat {
            let b = CGFloat(M_PI) * (self/180)
            return b
        }
    }
    
    extension UIBezierPath {
        convenience init(circleSegmentCenter center:CGPoint, radius:CGFloat, startAngle:CGFloat, endAngle:CGFloat)
        {
            self.init()
            self.moveToPoint(CGPointMake(center.x, center.y))
            self.addArcWithCenter(center, radius:radius, startAngle:startAngle.radians(), endAngle: endAngle.radians(), clockwise:true)
            self.closePath()
        }
    }
    
    
    
    func pieChart(pieces:[(UIBezierPath, UIColor)], viewRect:CGRect) -> UIView {
        var layers = [CAShapeLayer]()
        for p in pieces {
            let layer = CAShapeLayer()
            layer.path = p.0.CGPath
            layer.fillColor = p.1.CGColor
            layer.strokeColor = UIColor.whiteColor().CGColor
            layers.append(layer)
        }
        let view = UIView(frame: viewRect)
        for l in layers {
    
            view.layer.addSublayer(l)
    
    
        }
        return view
    }
    
    let rectSize = CGRectMake(0,0,400,400)
    let centrePointOfChart = CGPointMake(CGRectGetMidX(rectSize),CGRectGetMidY(rectSize))
    let radius:CGFloat = 100
    let piePieces = [(UIBezierPath(circleSegmentCenter: centrePointOfChart, radius: radius, startAngle: 250, endAngle: 360),UIColor.brownColor()), (UIBezierPath(circleSegmentCenter: centrePointOfChart, radius: radius, startAngle: 0, endAngle: 200),UIColor.orangeColor()), (UIBezierPath(circleSegmentCenter: centrePointOfChart, radius: radius, startAngle: 200, endAngle: 250),UIColor.lightGrayColor())]
    pieChart(piePieces, viewRect: CGRectMake(0,0,400,400))
    

    【讨论】:

    • 我正在使用此代码,它可以很好地绘制我的饼图。但是我在过去的 4 个小时里都在尝试将文本(例如“24%”)添加到各个切片中,并且根本无法显示任何文本......当然也没有以切片为中心。当我打印 layer.bounds 时,它们都显示为 0,0,0,0,所以我猜这会以某种方式剪切我的文本。但是我可以看到切片,因为 clipToBounds 默认为 false,所以这没有意义......所有提示都表示赞赏!
    • 嗨@Dewey,我写了一个粗略的gist,为您提供一种基于equation 添加带有定位的文本标签的方法。从这里您应该能够编写代码来创建标签数组并以更简洁的方式应用它们。希望对您有所帮助。
    【解决方案2】:

    您发布了一堆代码,似乎以单一颜色绘制单个饼图“切片”。

    你是说你不知道如何让它用不同大小的切片画一个完整的馅饼,并且你不知道如何让每个切片颜色不同?

    在我看来,您正在复制/粘贴从某个地方获得的代码,但不知道它是如何工作的。您如何引导我们了解您的代码的功能,让我们更清楚地了解您遇到的问题?

    我们不是来获取您的复制/粘贴代码并为您修改它以使其符合您的要求。对我来说听起来像是定制开发。我不知道这个板上的其他海报,但我得到了报酬。

    碰巧我写了一篇开发博客文章,其中包含一个在 Swift 中生成饼图的示例应用程序。你可以在这里看到它:

    http://wareto.com/swift-piecharts

    它不会像您发布的代码那样覆盖 drawRect,而是创建一个包含饼图的 CAShapeLayer。它管理具有可变数量“切片”的饼图,并且将更改每个切片的弧度、半径或两者。

    未设置为使每个切片具有不同的颜色。为此,您必须对其进行修改,以便为每个切片使用单独的形状层,这对程序来说是一个相当大的结构变化。

    它至少向您展示了如何在 iOS 版 Swift 中绘制饼图:

    【讨论】:

    • 谢谢,我会努力做的,我会努力理解的。我在抽奖方面有很大的困难。你有什么材料可以让我理解抽奖的东西吗?还是教程?再次感谢...
    • 我发布了一个指向博客文章的链接,其中包含工作应用程序的源代码。这应该是你需要的一切。
    【解决方案3】:

    下面的代码对于 swift 中的饼图切片空间很有用。签出一次

    导入 UIKit

    私人扩展 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)"
    }
    

    }

    /// 定义饼图的一部分

    结构段{

    /// The color of the segment
    var color : UIColor
    
    /// The name of the segment
    var name : String
    
    /// The value of the segment
    var value : CGFloat
    

    }

    类 PieChartView: 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();
        } // 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 : NSObject] = {
        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)
    }
    
    private var labels: [UILabel] = []
    private var totalValue: CGFloat = 1;
    
    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.5;
    
        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 {
    
                while diff != 0 {
                    var lbl: UILabel!
                    if labels.count <= 0 {
                        break;
                    }
                    lbl = labels.removeLast()
                    if lbl.superview != nil {
                        lbl.removeFromSuperview()
                    }
                    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 = "\(segments[i].value.formattedToOneDecimalPlace)%" //String.init(format: "%0.0f", segments[i].value)
            lbl.font = UIFont.systemFont(ofSize: 14)
        }
    }
    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) / 1.5
    
        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
    
        }
    }
    

    }

    【讨论】:

      猜你喜欢
      • 2011-02-01
      • 2015-04-30
      • 2016-02-29
      • 1970-01-01
      • 1970-01-01
      • 2019-06-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多