UCharts
前言
在移动应用开发中,数据可视化是提升用户体验的重要手段。随着 HarmonyOS 生态的快速发展,开发者们迫切需要一款功能强大、易于使用的图表库。今天为大家介绍一款优秀的开源第三方组件 —— UCharts,它为 HarmonyOS 开发者提供了丰富的图表解决方案。
介绍
UCharts 是一个基于 TypeScript 实现的类型丰富、高性能、模块化、可扩展、支持主题定制的跨平台图表库。底层渲染逻辑全部采用 TypeScript 实现,上层通过适配层(adapters)适配到不同平台,包括鸿蒙(HarmonyOS)、微信小程序、uniapp 等,真正实现"一套核心,多端复用"。
现已适配 HarmonyOS 平台。支持多种常用图表类型,满足鸿蒙应用的数据可视化需求。目前代码已经全部开源到Github,三方库中心仓,欢迎各位下载使用并提出宝贵意见!
背景
UCharts的前身uCharts是一款使用JavaScript基于canvas API开发的多平台前端应用图表库,已是2020年开发的版本。旧版本存在的一些架构问题和多端适配耦合问题,面对现如今高速发展的各种前端技术和平台,已不在具有技术优势。
UCharts重新设计了底层架构,模块化开发,核心层只实现与平台无关的渲染逻辑,独立的事件系统和动画系统。上层设计通过适配层适配到不同平台,解耦各个平台的交互逻辑。
用户只需要简单的配置,即可实现各种精美图表的绘制展示,满足各类应用的数据可视化需求。
特性
- 🧩 模块化设计:底层渲染与平台适配解耦,易于扩展和维护
- 🛡️ TypeScript 全面支持:类型安全,开发体验优秀
- 🎨 丰富图表类型:柱状图、条状图、折线图、区域图、山峰图等20种不同类型图表
- ⚡ 高性能渲染:底层优化,独立的动画和事件系统
- 🔌 易于扩展:支持自定义图表类型和平台适配
- 🍭 自定义样式:支持主题定制
体验预览
升级到 HarmonyOS NEXT 的手机, 可在应用商店搜索 best、ibest 关键字, 下载第一个名为 IBest-UI 的应用, 即可体验。
图表示例
以下为部分图表类型的鸿蒙平台实际渲染效果:
样例解析
图表绘制
如果想要绘制图表,只需要简单几个步骤即可实现:
1. 页面中添加图表组件,并为组件初始化控制器。
2. 设置图表配置,通过组件控制器更新图表
import { ChartOptions, UCharts, UChartsController } from '@ibestservices/ucharts'
@Entry
@Component
struct Index {
private chart: UChartsController = new UChartsController();
private opts: Partial<ChartOptions> = {
type: "column",
categories: ["2018","2019","2020","2021","2022","2023"],
series: [
{ name: "目标值", data: [35,36,31,33,13,34]},
{ name: "完成量", data: [18,27,21,24,6,28]}
],
xAxis: { disableGrid: true },
yAxis: { data: [{min: 0}] }
}
build() {
Column(){
UCharts({ controller: this.chart, onReady: () => {
this.chart.updateData(this.opts)
}})
/*
* 或者初始化时传入默认配置
* @State chart: UChartsController = new UChartsController(this.opts);
* UCharts({ controller: this.chart })
* */
}
}
}
扩展属性
每一种图表类型都有其对应的扩展属性配置,除了上面例子中的基础图表绘制。还可通过设置扩展属性来绘制各种丰富的图表样式。其中柱状图扩展属性配置项如下:
/**
* 柱状图扩展配置
*/
export interface ColumnExtra {
type: 'group'|'stack'|'meter' //柱状图类型,可选值:'group'分组柱状图,'stack'堆叠柱状图,'meter'温度计式图,默认group
width: number //柱状图每个柱子的图形宽度
seriesGap: number //多series每个柱子之间的间距
categoryGap: number //每个category点位(X轴点)柱子组之间的间距
barBorderCircle: boolean //启用分组柱状图半圆边框,默认false
barBorderRadius: [number,number,number,number] //自定义4个圆角半径[左上,右上,右下,左下]
linearType: 'none'|'opacity'|'custom' //渐变类型,可选值:"none"关闭渐变,"opacity"透明渐变,"custom"自定义颜色,默认none
linearOpacity: number //透明渐变的透明度(值范围0到1,值越小越透明),默认1
customColor: string[] //自定义渐变颜色,数组类型对应series的数组长度以匹配不同series颜色的不同配色方案,例如["#FA7D8D", "#EB88E2"]
colorStop: number //渐变色的显示比例(值范围0到1,值越大自定义颜色程度越高)
meterBorder: number //温度计式图表的边框宽度,默认1
meterFillColor: string //温度计式图表的空余填充颜色,默认#FFFFFF
activeBgColor: string //当前点击柱状图的背景颜色,默认#000000
activeBgOpacity: number //当前点击柱状图的背景颜色透明度,默认0.08
labelPosition: 'outside'|'insideTop'|'center'|'bottom' //数据标签位置,有效值为"outside"外部,"insideTop"内顶部,"center"内中间,"bottom"内底部,默认outside
}
目录结构
├── core/ # 图表核心能力(平台无关)
│ ├── types/ # 类型定义
│ ├── utils/ # 工具函数
│ ├── chart/ # 各类图表渲染器
│ ├── event/ # 事件系统
│ ├── animation/ # 动画系统
│ └── factory.ts # 图表工厂
├── adapters/ # 平台适配层
│ ├── harmony/ # 鸿蒙适配
│ ├── h5/ # 原生H5适配
│ ├── wechat/ # 微信小程序适配
│ └── uniapp/ # uniapp适配
├── interface/ # 对外统一接口
│ ├── CanvasContext.ts # 跨平台统一 canvas context 接口定义
├── examples/ # 示例代码
├── docs/ # 文档
└── README.md
跨平台适配层说明
- 每个平台在 adapters/ 下有独立目录,负责将平台 API 适配为统一的底层渲染接口。
- 适配层需实现统一的适配接口,并暴露标准的 context、事件等能力。
- 新增平台时,仅需在 adapters/ 下新增目录并实现适配接口,无需修改 core 层代码。
- CanvasContext 统一接口:
- ChartOptions.context 字段要求传入的 canvas context 必须兼容 interface/CanvasContext 类型。
- 各平台适配层需将平台原生 canvas context 封装/适配为该接口,保证 core 层渲染逻辑的统一调用。
- 具体接口定义见 interface/CanvasContext.ts,如需适配新平台,请实现该接口。
鸿蒙平台的适配实现
适配 CanvasContext 类型
使用代理模式,实现CanvasContext适配器,拦截 HarmonyOS 平台不支持的属性,进行兼容处理。
// 定义CanvasRenderingContext2D的CanvasContext适配器
class CanvasContextProxy implements ProxyHandler<CanvasRenderingContext2D>, ProxyHandler<CanvasContext> {
// 拦截属性设置操作
set(target: CanvasRenderingContext2D|CanvasContext, propertyKey: string, value: string, receiver: AnyType): AnyType {
// 确保只设置存在的可写属性
if (typeof propertyKey === 'string') {
try {
(target as AnyType)[propertyKey] = value;
return true;
} catch (e) {
console.warn(`无法设置属性 ${String(propertyKey)}`, e);
}
}
return false;
}
get(target: CanvasRenderingContext2D|CanvasContext, propertyKey: string, receiver: AnyType): AnyType {
if (propertyKey === "createCircularGradient") {
return undefined
}
const value: AnyType = (target as AnyType)[propertyKey];
return typeof value === 'function' ? value.bind(target) : value;
}
}
使用CanvasContextProxy适配CanvasRenderingContext
const handler = new CanvasContextProxy();
const context = new CanvasRenderingContext2D(new RenderingContextSettings(true));
const canvasContext = new Proxy(context, handler) as CanvasContext;
适配点击和触屏交互
通过将UCharts组件的点击事件和触摸事件坐标点适配到对应核心层接口
onMouseEvent(event: MouseEvent) {
const p: Point = {
x: event.x,
y: event.y
}
if(event.action == MouseAction.Press) {
this.chartRenderer?.scrollStart(p)
this.chartRenderer?.touchLegend(p)
this.chartRenderer?.showToolTip(p)
} else if(event.action == MouseAction.Move) {
this.chartRenderer?.scroll(p)
if(!this.opts?.enableScroll) this.chartRenderer?.showToolTip(p)
} else if(event.action == MouseAction.Release) {
this.chartRenderer?.scrollEnd()
}
}
onTouchEvent(event: TouchEvent) {
const touch = event.touches[0]
const p: Point = {
x: touch.x,
y: touch.y
}
if(event.type == TouchType.Down) {
this.chartRenderer?.scrollStart(p)
this.chartRenderer?.touchLegend(p)
this.chartRenderer?.showToolTip(p)
} else if(event.type == TouchType.Move) {
this.chartRenderer?.scroll(p)
if(event.touches.length > 1) {
this.chartRenderer?.doubleZoom([
{x: event.touches[0].x, y: event.touches[0].y},
{x: event.touches[1].x, y: event.touches[1].y}
])
}
if(!this.opts?.enableScroll) this.chartRenderer?.showToolTip(p)
} else if(event.type == TouchType.Up) {
this.chartRenderer?.scrollEnd()
}
}
最终实现 HarmonyOS 平台的适配。
未来发展
随着 HarmonyOS 生态的不断完善,UCharts 也在持续演进:
更多图表类型:不断增加新的图表类型支持
性能优化:持续优化渲染性能和内存使用
交互增强:提供更丰富的交互功能
主题系统:完善的主题定制系统
对于正在开发 HarmonyOS 应用的开发者来说,UCharts 无疑是一个值得尝试的优秀第三方库。它不仅能够帮助您快速实现数据可视化功能,还能为您的应用带来更好的用户体验。
致谢
感谢所有开源贡献者和用户的支持!希望这篇文章能够帮助您更好地了解和使用 UCharts,在 HarmonyOS 开发中创造出更加精彩的数据可视化应用!