当前位置: 首页 > news >正文

ArkUI实战,自定义饼状图组件PieChart

本节笔者带领读者实现一个饼状图 PieChart 组件,该组件是根据笔者之前封装的 MiniCanvas 实现的, PieChart 的最终演示效果如下图所示:
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();// 初始化delegatethis.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 = 270;if (this.direction == RotateDirection.RIGHT) {result = 0;}if (this.direction == RotateDirection.BOTTOM) {result = 270;}if (this.direction == RotateDirection.LEFT) {result = 180;}if (this.direction == RotateDirection.TOP) {result = 90;}return result;}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;}
}

以上就是笔者介绍的实现一个饼状图的思路和实现,读者可以阅读源码,目前 PieChart 在选中饼状块并突出时没有动画特效而是直接旋转过来了,后续笔者会把旋转的动效加上。

相关文章:

ArkUI实战,自定义饼状图组件PieChart

本节笔者带领读者实现一个饼状图 PieChart 组件&#xff0c;该组件是根据笔者之前封装的 MiniCanvas 实现的&#xff0c; PieChart 的最终演示效果如下图所示&#xff1a; 饼状图实现的拆分 根据上图的样式效果&#xff0c;实现一个饼状图&#xff0c;实质就是绘制一个个的实…...

工作实战之系统交互api调用认证设计

目录 前言 一、黄金段位接口交互 二、钻石段位接口交互设计 1.接口文档定义 2.工具类以及demo提供 a.调用方部分代码 b.被调用方 三.星耀段位接口访问设计 1.在钻石段位的基础上&#xff0c;进行sdk的封装 a.maven引入 b.sdk包含工具类 四.王者段位接口访问设计 1.开发详情 2.…...

学习系统编程No.5【虚拟地址空间】

引言: 北京时间&#xff1a;2023/2/22&#xff0c;离补考期末考试还有5天&#xff0c;不慌&#xff0c;刚午觉睡醒&#xff0c;闹钟2点20&#xff0c;拖到2点50&#xff0c;是近以来&#xff0c;唯一一次有一种睡不醒的感觉&#xff0c;但是现在却没有精神&#xff0c;因为听了…...

Linux常用指令(未完待续。。。)

* basename&#xff1a;只留下路径的“最后一部分” X、文件夹&目录操作 复制 &#xff1a;cp /xxx /xxx - a 该选项通常在拷贝目录时使用。它保留链接、文件属性&#xff0c;并递归地拷贝目录&#xff0c;其作用等于dpR选项的组合&#xff1b; - d 拷贝时保留链接&#…...

用D写裸机

原文 用D编写裸机RISC-V应用 这篇文章展示,如何用D编写,目标为RISC-VQEMU模拟器的程序裸机"你好".项目 为什么是D? 我最近一直在用C编写裸机代码,我有点对C缺乏特征感到沮丧.D引入了叫betterC的模式(基本上禁止了D运行时的所有语言功能).使得D裸机编程大致与C一…...

(二十五)、实现评论功能(5)【uniapp+uinicloud多用户社区博客实战项目(完整开发文档-从零到完整项目)】

1&#xff0c;实现二级回复的入库操作 1.1 两个子组件&#xff08;comment-item和comment-frame&#xff09;与父组件reply之间的属性传值 comment-item&#xff1a; props: {item: {type: Object,default () {return {}}}},comment-frame&#xff1a; props: {commentObj: {…...

【概念辨析】二维数组传参的几种可能性

一、二维数组传参竟然不是用二级指针进行接收&#xff1f; 今天进行再一次的二级指针学习时&#xff0c;发现了一条以前没怎么注意过的知识点&#xff1a;二维数组进行传参只能用二维数组&#xff08;不能省略列&#xff09;进行接收或者是数组指针。 问题复现代码如下&#xf…...

python和C++代码实现图片九宫格切图程序(附VS2015配置Opencv教程)

1、python代码实现图片分割成九宫格 需要包含的库&#xff0c;没有下载安装的&#xff0c;需要自己安装哦。 实现原理很简单&#xff0c;就是用PIL库不断画小区域&#xff0c;切下来存储成新的小图片。 假设每一个格子的宽和高分别是w、h&#xff0c;那么第row行&#xff08…...

【深度学习】优化器

1.什么是优化器 优化器是在深度学习的反向传播过程中&#xff0c;指引损失函数&#xff08;目标函数&#xff09;的各个参数往正确的方向更新合适的大小&#xff0c;使得更新后的各个参数让目标函数不断逼近全局最小点。 2.优化器 2-1 BGD 批量梯度下降法&#xff0c;是梯度下…...

SpringBoot使用validator进行参数校验

Validated、Valid和BindingResultBean Validation是Java定义的一套基于注解的数据校验规范&#xff0c;比如Null、NotNull、Pattern等&#xff0c;它们位于 javax.validation.constraints这个包下。hibernate validator是对这个规范的实现&#xff0c;并增加了一些其他校验注解…...

论文复现:风电、光伏与抽水蓄能电站互补调度运行(MATLAB-Yalmip全代码)

论文复现:风电、光伏与抽水蓄能电站互补调度运行(MATLAB-Yalmip全代码) 针对风电、光伏与抽水蓄能站互补运行的问题,已有大量通过启发式算法寻优的案例,但工程上更注重实用性和普适性。Yalmip工具箱则是一种基于MATLAB平台的优化软件工具箱,被广泛应用于工程界优化问题和…...

FastCGI sent in stderr: "PHP message: PHP Fatal error

服务器php7.2卸载安装7.4之后,打开网站一直无法访问,查看nginx错误日志发现一直报这个错误:2023/02/23 11:12:55 [error] 4735#0: *21 FastCGI sent in stderr: &#xff02;PHP message: PHP Fatal error: Uncaught ReflectionException: Class translator does not exist in …...

【数字IC基础】跨时钟域(CDC,Clock Domain Crossing)

文章目录 一、什么是跨时钟域?二、跨时钟域传输的问题?2、1 亚稳态(单bit:两级D触发器(双DFF))2、2 数据收敛(多bit亚稳态)(格雷码编码、握手协议、异步FIFO、DMUX)2、3 多路扇出:(先同步后扇出)2、4 数据丢失(延长输入数据信号):类似脉冲展宽2、5 异步复位(…...

UNI-APP学习

uni-app的基本使用 uni-app介绍 官方网页 uni-app 是一个使用 Vue.js 开发所有前端应用的框架&#xff0c;开发者编写一套代码&#xff0c;可发布到iOS、Android、H5、以及各种小程序&#xff08;微信/支付宝/百度/头条/QQ/钉钉&#xff09;等多个平台。 即使不跨端&#xf…...

编译原理【运行时环境】—什么是活动记录、 活动记录与汇编代码的关系

系列文章戳这里&#x1f447; 什么是上下文无关文法、最左推导和最右推导如何判断二义文法及消除文法二义性何时需要消除左递归什么是句柄、什么是自上而下、自下而上分析什么是LL(1)、LR(0)、LR(1)文法、LR分析表LR(0)、SLR(1)、LR(1)、LALR(1)文法之间的关系编译原理第三章习…...

【Windows Server 2019】发布服务器 | 远程桌面服务的安装与配置 Ⅰ——理论,实验拓扑和安装基于RemoteAPP的RDS

目录1. 理论1.1 什么是远程桌面服务2. 实验拓扑2.1 拓扑说明3. 安装基于RemoteAPP的RDS1. 理论 1.1 什么是远程桌面服务 远程桌面服务 (RDS) 是一个卓越的平台&#xff0c;可以生成虚拟化解决方案来满足每个最终客户的需求&#xff0c;包括交付独立的虚拟化应用程序、提供安全…...

Bootstrap入门到精通(最全最详细)

文章目录前言一、Bootstrap是什么&#xff1f;二、Bootstrap安装方式一&#xff1a;将压缩包下载到本地引入使用方式二&#xff1a;使用Bootstrap官方cdn二.Bootstrap容器下面是屏幕宽度在不同大小时不同容器的显示状态三.Bootstrap栅格系统bootstrap网格系统有以下六个类网格系…...

C/C++每日一练(20230223)

目录 1. 数据合并 2. 回文链表 3. 完美矩形 1. 数据合并 题目描述 将两个从小到大排列的一维数组 (维长分别为 m,n , 其中 m,n≤100) 仍按从小到大的排列顺序合并到一个新的一维数组中&#xff0c;输出新的数组. 输入描述 第 1 行一个正整数 m , 表示第一个要合并的一维…...

c语言中const 是什么意思?(面试)

const关键字使用非常的灵活&#xff0c;在c中&#xff0c;const因位置不同有不同的作用&#xff0c;因情景不同有不同的角色&#xff0c;使用起来也是非常的灵活。 可以定义const常量&#xff0c;具有不可变性。 例如&#xff1a;const int Max100; Max会产生错误; 便于进行类…...

网络工程(三)ensp配置静态路由

配置静态路由 这里选择的路由器是AR2220 因为有三个GE接口 下面说拓扑图 一、定义AR路由ip地址和下一条 AR1system-viewsysname AR1interface g0/0/0ip address 10.0.0.254 8interface g0/0/1ip address 50.0.0.1 8下一条代码[AR1]ip route-static 0.0.0.0 0 50.0.0.2AR2 s…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合

强化学习&#xff08;Reinforcement Learning, RL&#xff09;是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程&#xff0c;然后使用强化学习的Actor-Critic机制&#xff08;中文译作“知行互动”机制&#xff09;&#xff0c;逐步迭代求解…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

深入理解JavaScript设计模式之单例模式

目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式&#xff08;Singleton Pattern&#…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...