苹果在年推出了SwiftUI,它是一种轻量级且易于使用的用户界面创建方式。
本文探讨了SwiftUI框架如何帮助我们构建干净、简单且令人惊叹的数据可视化工具。这一次,我们来看看如何制作饼图!
什么是饼图?
饼图,也称为圆图,是一种数据可视化,它将每个数据值表示为一个按比例大小的圆片。与条形图一样,它们处理分类数据,如果不同类别之间的比率对于传达信息至关重要,则是一个很好的工具。
我们在本文中构建的饼图的一个示例。
这种类型的可视化可能适用于以下情况:
想象一下公司每个部门的年度预算是多少。试着弄清楚你的月薪是如何在付房租、付伙食费和买书籍之间进行分配的。在你所在城市的不同区域制定出租公寓和公寓的比例。我们需要什么?
要制作饼图,我们需要SwiftUI的Path、Shape和ZStack类型。接下来进入实现部分。
代码
让我们先看看主要组件,即PieChart结构。
importSwiftUIstructPieChart:View{
Bindingvardata:[Double]Bindingvarlabels:[String]privateletcolors:[Color]privateletborderColor:ColorprivateletsliceOffset:Double=-.pi/2init(data:Binding[Double],labels:Binding[String],colors:[Color],borderColor:Color){self._data=dataself._labels=labelsself.colors=colorsself.borderColor=borderColor}varbody:someView{GeometryReader{geoinZStack(alignment:.center){ForEach(0..data.count){indexinPieSlice(startAngle:startAngle(for:index),endAngle:endAngle(for:index)).fill(colors[index%colors.count])PieSlice(startAngle:startAngle(for:index),endAngle:endAngle(for:index)).stroke(borderColor,lineWidth:1)PieSliceText(title:\(labels[index]),description:String(format:%.2fmillion,data[index])).offset(textOffset(for:index,in:geo.size)).zIndex(1)}}}}privatefuncstartAngle(forindex:Int)-Double{switchindex{case0:returnsliceOffsetdefault:letratio:Double=data[..index].reduce(0.0,+)/data.reduce(0.0,+)returnsliceOffset+2*.pi*ratio}}privatefuncendAngle(forindex:Int)-Double{switchindex{casedata.count-1:returnsliceOffset+2*.pidefault:letratio:Double=data[..(index+1)].reduce(0.0,+)/data.reduce(0.0,+)returnsliceOffset+2*.pi*ratio}}privatefunctextOffset(forindex:Int,insize:CGSize)-CGSize{letradius=min(size.width,size.height)/3letdataRatio=(2*data[..index].reduce(0,+)+data[index])/(2*data.reduce(0,+))letangle=CGFloat(sliceOffset+2*.pi*dataRatio)returnCGSize(width:radius*cos(angle),height:radius*sin(angle))}}这个结构包含相当多的帮助函数,其中一些函数做了一些乍一看可能很吓人的数学运算,但我们将详细介绍一下。由于我们将数据表示为圆形扇区,因此需要计算它们的每个起始角和结束角。我们对这些任务使用startAngle(for:)和endAngle(for:)方法,它们以弧度返回值。
textOffset(for:in:)方法看起来更复杂。然而在现实中,它与角度法非常相似。它的任务是计算圆中的一个点,在这里我们可以放置描述不同圆扇区代表什么的标签。这些计算有两个值得一提的特性:
他们把标签放在圆心和圆周之间三分之二的地方。它们将标签放置在其相关圆扇区的中心。现在,让我们看看body属性,看看它是如何构造的。首先将所有内容包装在GeometryReader中。该组件提供了对包含矩形的信息的访问,我们需要这些信息来正确放置标签。
GeometryReader的内部是一个ZStack,它允许我们将几个组件放在彼此的顶部。
ZStack承载一个ForEach视图,该视图反过来为所有数据点创建PieSlice组件和PieSliceText。注意,ForEach在每次运行时实例化两个PieSlice对象。在撰写本文时,SwiftUI并没有提供一种很好的方法来绘制和填充单个形状,这迫使我们创建两个对象—一个填充,一个用作轮廓。
最后要注意的是,我们指定了文本的Z索引,以确保它们呈现在任何其他组件之上。
绘制数据
PieChart视图在计算所有封闭组件的角度和位置方面做了很多繁重的工作。多亏了这一点,实现PieSlice形状和PieSliceText标签就轻而易举了。看看PieSlice的代码。
importSwiftUIstructPieSlice:Shape{letstartAngle:DoubleletendAngle:Doublefuncpath(inrect:CGRect)-Path{varpath=Path()letradius=min(rect.width,rect.height)/2letalpha=CGFloat(startAngle)letcenter=CGPoint(x:rect.midX,y:rect.midY)path.move(to:center)path.addLine(to:CGPoint(x:center.x+cos(alpha)*radius,y:center.y+sin(alpha)*radius))path.addArc(center:center,radius:radius,startAngle:Angle(radians:startAngle),endAngle:Angle(radians:endAngle),clockwise:false)path.closeSubpath()returnpath}}PieSlice在实例化时将开始角度和结束角度作为参数。借助三角学,它画出一个圆扇形,适合于这些角度之间。这里没有什么新奇的东西-只是普通的旧直角三角形数学。
最后要检查的是文本组件。它唯一的工作就是把两个文本标签叠在一起——这项任务执行得非常精确。
importSwiftUIstructPieSliceText:View{lettitle:Stringletdescription:Stringvarbody:someView{VStack{Text(title).font(.headline)Text(description).font(.body)}}}把它们放在一起
我们在本文中组合的PieChart视图可以按原样使用了。你所要做的就是将文件复制到项目中并实例化一个新对象。你可以随意尝试这些组件,添加新组件,并为图表提供你自己的个人风格。
你可以阅读本系列的前两篇文章: