快速的饼图切片

时间:2015-05-15 18:38:32

标签: ios swift charts

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

这是我绘制图表的代码(我在这里堆叠):

   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)



    }


}

3 个答案:

答案 0 :(得分:3)

此代码来自我的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))

enter image description here

答案 1 :(得分:2)

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

你是说你不知道如何用不同大小的切片绘制整个馅饼,而你不知道如何使每个切片成为不同的颜色?

对我而言,就像你复制/粘贴你从某个地方获得的代码并且不知道它是如何工作的。你告诉我们你的代码做了什么,让我们更清楚地知道你被困在哪里?

我们不会在此处提取您的复制/粘贴代码并对其进行修改以使其符合您的要求。对我来说听起来像是定制开发。我不知道这块板子上的其他海报,但我得到了报酬。

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

http://wareto.com/swift-piecharts

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

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

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

enter image description here

答案 2 :(得分:0)

以下代码对于快速饼图切片空间很有用。结帐一次

导入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

    }
}

}