ArkUI实战,自定义饼状图组件PieChart
Posted llew2011
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ArkUI实战,自定义饼状图组件PieChart相关的知识,希望对你有一定的参考价值。
本节笔者带领读者实现一个饼状图 PieChart
组件,该组件是根据笔者之前封装的 MiniCanvas 实现的, PieChart
的最终演示效果如下图所示:
饼状图实现的拆分
根据上图的样式效果,实现一个饼状图,实质就是绘制一个个的实心圆弧加上圆弧对应颜色就搞定了,圆弧的大小是根据饼状的数据分布计算出来的,对应的颜色自己指定就可以了,其次手指点击到饼状图,需要找到对应的饼状块并突出显示,找到饼状块先计算手指点击坐标和圆弧中心的夹角,根据夹角和每个圆弧的大小找到对应的圆弧,找到圆弧后计算圆弧的突出偏移量并重置所有饼状块的圆弧起始值就可以了。
- 计算夹角
计算夹角就是计算手指点击饼状图上的坐标 (x, y) 和饼状图的圆心坐标 (centerX, centerY) 之间的顺时针角度,计算方法如下所示:
private getTouchedAngle(centerX: number, centerY, x: number, y: number)
var deltaX = x - centerX;
var deltaY = centerY - y;
var t = deltaY / Math.sqrt(deltaX * deltaX + deltaY * deltaY);
var angle = 0;
if (deltaX > 0)
if (deltaY > 0)
angle = Math.asin(t);
else
angle = Math.PI * 2 + Math.asin(t);
else if (deltaY > 0)
angle = Math.PI - Math.asin(t);
else
angle = Math.PI - Math.asin(t);
return 360 - (angle * 180 / Math.PI) % 360;
- 找圆弧块
计算出手指点击位置和圆心的夹角后,遍历每一个饼状块做比较就可以了,代码如下所示:
private getTouchedPieItem(angle: number): PieItem
for(var i = 0; i < this.pieItems.length; i++)
var item = this.pieItems[i];
if(item.getStopAngle() < 360)
if(angle >= item.getStartAngle() && angle < item.getStopAngle())
return item;
else
if(angle >= item.getStartAngle() && angle < 360 || (angle >= 0 && angle < item.getStopAngle() - 360))
return item;
return null;
- 计算偏移量
找到圆弧块后,根据圆弧块的圆弧大小,计算出该圆弧突出后的偏移量,代码如下所示:
private calculateRoteAngle(item: PieItem): number
var result = item.getStartAngle() + item.getAngle() / 2 + this.getDirectionAngle();
if (result >= 360)
result -= 360;
if (result <= 180)
result = -result;
else
result = 360 - result;
return result;
- 重置偏移量
有了目标圆弧块的偏移角度后,重置每一个圆弧块的起始偏移量就可以了,代码如下所示:
private resetStartAngle(angle: number)
this.pieItems.forEach((item) =>
item.setSelected(false);
item.setStartAngle(item.getStartAngle() + angle);
);
- 重新绘制圆弧
绘制圆弧使用 MiniCanvas 提供的drawArc()
方法即可,代码如下所示:
drawPieItem()
this.pieItems.forEach((item) =>
this.paint.setColor(item.color);
var x = this.calculateCenterX(item.isSelected());
var y = this.calculateCenterY(item.isSelected());
this.canvas.drawArc(x, y, this.radius, item.getStartAngle(), item.getStopAngle(), this.paint);
)
饼状图的实现
拆分完饼状图的步骤后,实现起来就方便多了, PieChart
的完整代码如下所示:
import MiniCanvas, Paint, ICanvas from './icanvas'
@Entry @Component struct PieChart
private delegate: PieChartDelegate;
build()
Column()
MiniCanvas(
attribute:
width: this.delegate.calculateWidth(),
height: this.delegate.calculateHeight(),
clickListener: (event) =>
// 根据点击绘制突出的饼状块
this.delegate.onClicked(event.x, event.y);
,
onDraw: (canvas) =>
// 开始绘制
this.delegate.setCanvas(canvas);
this.delegate.drawPieItem();
)
.padding(10)
.size(width: "100%", height: "100%")
aboutToAppear()
// mock测试数据
var pieItems = PieItem.mock();
// 初始化delegate
this.delegate = new PieChartDelegate(pieItems, RotateDirection.BOTTOM);
// 定义饼状块的属性,包括角度,起始角度,占比,颜色,是否选中突出
export class PieItem
private startAngle: number = 0;
private rate: number = 0;
private angle: number = 0;
private selected: boolean = false;
constructor(public count: number, public color: string)
setSelected(selected: boolean)
this.selected = selected;
return this;
isSelected()
return this.selected;
setStartAngle(startAngle: number)
this.startAngle = startAngle > 360 ? startAngle - 360 : startAngle < 0 ? 360 + startAngle : startAngle;
return this;
getStartAngle()
return this.startAngle;
getStopAngle()
return this.startAngle + this.angle;
setRate(rate: number)
this.rate = rate;
return this;
getRate()
return this.rate;
setAngle(angle: number)
this.angle = angle;
return this;
getAngle()
return this.angle;
// mock一份测试数据
static mock(): Array<PieItem>
var pieItems = new Array<PieItem>();
pieItems.push(new PieItem(21, "#6A5ACD"))
pieItems.push(new PieItem(18, "#20B2AA"))
pieItems.push(new PieItem(29, "#FFFF00"))
pieItems.push(new PieItem(12, "#00BBFF"))
pieItems.push(new PieItem(20, "#DD5C5C"))
pieItems.push(new PieItem(13, "#8B668B"))
return pieItems;
// 饼状块的突出方向
export enum RotateDirection
LEFT,
TOP,
RIGHT,
BOTTOM
// 饼状图绘制的具体实现类
class PieChartDelegate
private paint: Paint;
private canvas: ICanvas;
constructor(private pieItems: Array<PieItem>, private direction: RotateDirection = RotateDirection.BOTTOM, private offset: number = 10, private radius: number = 80)
this.calculateItemAngle();
setPitItems(pieItems: Array<PieItem>)
this.pieItems = pieItems;
setCanvas(canvas: ICanvas)
this.canvas = canvas;
this.paint = new Paint();
onClicked(x: number, y: number)
if(this.canvas)
var touchedAngle = this.getTouchedAngle(this.radius, this.radius, x, y);
var touchedItem = this.getTouchedPieItem(touchedAngle);
if(touchedItem)
var rotateAngle = this.calculateRoteAngle(touchedItem);
this.resetStartAngle(rotateAngle);
touchedItem.setSelected(true)
this.clearCanvas();
this.drawPieItem();
else
console.warn("canvas invalid!!!")
clearCanvas()
this.canvas.clear();
drawPieItem()
this.pieItems.forEach((item) =>
this.paint.setColor(item.color);
var x = this.calculateCenterX(item.isSelected());
var y = this.calculateCenterY(item.isSelected());
this.canvas.drawArc(x, y, this.radius, item.getStartAngle(), item.getStopAngle(), this.paint);
)
calculateWidth(): number
if (this.direction == RotateDirection.LEFT || this.direction == RotateDirection.RIGHT)
return this.radius * 2 + this.offset;
else
return this.radius * 2;
calculateHeight(): number
if (this.direction == RotateDirection.TOP || this.direction == RotateDirection.BOTTOM)
return this.radius * 2 + this.offset;
else
return this.radius * 2;
private calculateCenterX(hint: boolean): number
if(this.direction == RotateDirection.LEFT)
return hint ? this.radius : this.radius + this.offset;
else if(this.direction == RotateDirection.TOP)
return this.radius;
else if(this.direction == RotateDirection.RIGHT)
return hint ? this.radius + this.offset : this.radius;
else
return this.radius;
private calculateCenterY(hint: boolean): number
if(this.direction == RotateDirection.LEFT)
return this.radius;
else if(this.direction == RotateDirection.TOP)
return hint ? this.radius : this.radius + this.offset;
else if(this.direction == RotateDirection.RIGHT)
return this.radius;
else
return hint ? this.radius + this.offset : this.radius;
private resetStartAngle(angle: number)
this.pieItems.forEach((item) =>
item.setSelected(false);
item.setStartAngle(item.getStartAngle() + angle);
);
private calculateRoteAngle(item: PieItem): number
var result = item.getStartAngle() + item.getAngle() / 2 + this.getDirectionAngle();
if (result >= 360)
result -= 360;
if (result <= 180)
result = -result;
else
result = 360 - result;
return result;
private calculateItemAngle()
var total = 0;
this.pieItems.forEach((item) =>
total += item.count;
)
for(var i = 0; i < this.pieItems.length; i++)
var data = this.pieItems[i];
data.setRate(data.count / total);
data.setAngle(data.getRate() * 360);
if (i == 0)
data.setStartAngle(0);
else
var preData = this.pieItems[i - 1];
data.setStartAngle(preData.getStopAngle());
private getDirectionAngle(): number
var result 以上是关于ArkUI实战,自定义饼状图组件PieChart的主要内容,如果未能解决你的问题,请参考以下文章