# mini5 **Repository Path**: xubobovip/mini5 ## Basic Information - **Project Name**: mini5 - **Description**: 鸿蒙版股票折线图 - **Primary Language**: TypeScript - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2024-06-14 - **Last Updated**: 2025-05-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### Canvas绘制带阴影的股票折线图 ArkUI基于api11实现效果如下: ![这是图片](./img/mini5@2x.png "lazyGroup") ##### 具体实现 ###### 1,先准备数据源 主要是这个Trand[],是我们点位的原始数据 ```typescript const fenShiModel: FenShiModel = new FenShiModel("00700", "hk", 9.06, [ new Trend(0.02, 9.08, '2024-05-16 09:35:00'), new Trend(0.0, 9.06, '2024-05-16 09:40:00'), new Trend(0.06, 9.12, '2024-05-16 09:45:00'), new Trend(0.08, 9.14, '2024-05-16 09:50:00'), new Trend(0.12, 9.18, '2024-05-16 09:55:00'), new Trend(0.12, 9.18, '2024-05-16 10:00:00'), new Trend(0.13, 9.19, '2024-05-16 10:05:00'), new Trend(0.15, 9.21, '2024-05-16 10:10:00'), new Trend(0.12, 9.18, '2024-05-16 10:15:00'), new Trend(0.12, 9.18, '2024-05-16 10:20:00'), new Trend(0.13, 9.19, '2024-05-16 10:25:00'), new Trend(0.15, 9.21, '2024-05-16 10:30:00'), new Trend(0.16, 9.22, '2024-05-16 10:35:00'), new Trend(0.15, 9.21, '2024-05-16 10:40:00'), new Trend(0.15, 9.21, '2024-05-16 10:45:00'), new Trend(0.15, 9.21, '2024-05-16 10:50:00'), ]) ``` ###### 2,创建一个DataManager类,统一处理计算逻辑 setNewData()设置初始值,然后处理最大值和最小值相等的情况 ```typescript /** * 设置数据 */ setNewData(fenShi: FenShiModel): void { // 重置数据 this.preClose = 0.0; this.fenShiDataList = fenShi.trends; if (this.fenShiDataList && this.fenShiDataList.length > 0) { this.extreme.init(this.fenShiDataList[0].price); this.extreme.convert(this.fenShiDataList); this.preClose = fenShi.preClose === 0.0 ? 1.0 : fenShi.preClose || 1.0; // 重置最值 this.resetPeakPrice(); } } /** * 重置最值,处理最大最小相等的情况 */ private resetPeakPrice(): void { if (this.extreme.maximum === this.extreme.minimum) { const oldEqualMax = this.extreme.maximum; if (this.extreme.maximum === 0.0) { // 最大最小为0时特殊处理 this.extreme.maximum = 1.0; this.extreme.minimum = 1.0; } this.extreme.minimum = this.extreme.maximum / 2; this.extreme.maximum = this.extreme.maximum * 3 / 2; if (this.preClose !== oldEqualMax) { // 将昨收调小,调到范围外即可 this.preClose = this.extreme.minimum - 0.2; } } this.extreme.calculatePeek(); } ``` calculatePointList(),就是根据原始数据计算出在坐标轴中的位置,得到ViewPoin[] ```typescript /** * 计算坐标点 * * 美股:9:35 - 12:00 - 16:00 (30 + 48 = 78) * * 港股:9:35 - 12:00 13:00 - 16:00 (30 + 36 = 66) */ calculatePointList(width: number, height: number, fenShi: FenShiModel): void { const xPeriod = width / (fenShi.market === "hk" ? 66 : 78); const trends = fenShi.trends; this.preCloseY = ((this.preClose - this.extreme.minimum) / this.extreme.peek) * height; this.points = []; if (trends && trends.length > 0) { const isHalf = false trends.forEach((it, index) => { const indexOffset = isHalf ? index + 30 : index; const price = it.price === 0.0 ? 1.0 : it.price; this.points.push( new ViewPoint( xPeriod * indexOffset, ((price - this.extreme.minimum) / this.extreme.peek) * height ) ); }); } } ``` ###### 3,最后定义mini5组件,处理canvas逻辑 代码还是很好懂的: 1,先是定义不同模式的颜色 2,计算坐标点数组 3,变化画布,变成我们初中时熟知的直角坐标系的样子 ![这是图片](./img/xy1@2x.png "lazyGroup") ![这是图片](./img/xy2@2x.png "lazyGroup") 4,定义两条Path:一个折线,一个阴影 5,根据数据的数量采取不同的处理方式: a,只有一条数据的时候,绘制一个点 b,多条数据的时候,先moveTo()到第一个点,然后循环lineTo()折线 6,最后,绘制两条path就可以了。 ```typescript @Component export default struct Mini5 { @Prop fenShi: FenShiModel @Prop mode: string private contextSettings = new RenderingContextSettings(true) private canvas = new CanvasRenderingContext2D(this.contextSettings) build() { Canvas(this.canvas) .onReady(() => { let curveLineColor = "#00000000" let shadowTopColor = "#00000000" let shadowBottomColor = "#00000000" // 根据不同的涨跌平的模式,显示不同色颜色 if (this.mode == "up") { shadowTopColor = "#33EF5150" shadowBottomColor = "#00EF5150" curveLineColor = "#FFEF5150" } else if (this.mode == "down") { shadowTopColor = "#332FAA2E" shadowBottomColor = "#002FAA2E" curveLineColor = "#FF2FAA2E" } else { shadowTopColor = "#4DACACAD" shadowBottomColor = "#00ACACAD" curveLineColor = "#FFACACAD" } const height = this.canvas.height const width = this.canvas.width // 初始化数据 const fenShiDataManager = new MiniFenShiDataManager() fenShiDataManager.setNewData(this.fenShi) fenShiDataManager.calculatePointList(width, height, this.fenShi) // 变换画布 变成我们初中时熟知的直角坐标系的样子 this.canvas.translate(0, height) this.canvas.scale(1, -1) // 折线path const curvePath = new Path2D() // 渐变阴影path const gradiantColorPath = new Path2D() const pointSize = fenShiDataManager.points.length if (pointSize > 0) { if (pointSize == 1) { // 处理只有一个点时的情况 const firstPoint = fenShiDataManager.points[0] const circlePath = new Path2D() circlePath.arc(firstPoint.x + 0.5, firstPoint.y, 0.5, 0, 2 * Math.PI) this.canvas.strokeStyle = curveLineColor this.canvas.fill(circlePath) } else { // 循环操作path,path的一些常用操作 moveTo lineTo等等 fenShiDataManager.points.forEach((point, index) => { if (index == 0) { curvePath.moveTo(point.x, point.y) gradiantColorPath.moveTo(point.x, point.y) } else { curvePath.lineTo(point.x, point.y) gradiantColorPath.lineTo(point.x, point.y) if (index == pointSize - 1) { // 最后一个点多做两个操作 gradiantColorPath.lineTo(point.x, 0) gradiantColorPath.lineTo(0, 0) gradiantColorPath.closePath() } } }) // 绘制折线 this.canvas.strokeStyle = curveLineColor this.canvas.stroke(curvePath) // 绘制渐变阴影,从上向下绘制 const maxHeightPoint = fenShiDataManager.points.reduce((acc, curr) => acc.y > curr.y ? acc : curr); const gradient = this.canvas.createLinearGradient(maxHeightPoint.x, maxHeightPoint.y, maxHeightPoint.x, 0) this.canvas.beginPath() gradient.addColorStop(0, shadowTopColor) gradient.addColorStop(1, shadowBottomColor) this.canvas.fillStyle = gradient this.canvas.fill(gradiantColorPath) } } }) } } ```