效果图

9f4b8a789f3d84bbbf94aaf4d53844e4.jpg
话不多说,直接上代码
// 定义数据项接口(可选,用于类型约束)
interface DataItem {
x: string; // 月份
y: number; // 数值
}
@Entry
@Component
export struct LineChartExample {
private renderingSettings: RenderingContextSettings = new RenderingContextSettings(true)
private canvas: CanvasRenderingContext2D =
new CanvasRenderingContext2D(this.renderingSettings) // CanvasRenderingContext2D对象
// 画布尺寸配置
private canvasWidth: number = 360
private canvasHeight: number = 250
private top = 30
private right = 30
private bottom = 30
private left = 30
// 鸿蒙中定义私有数据数组
private data: DataItem[] = [
{ x: '1月', y: 120 },
{ x: '2月', y: 190 },
{ x: '3月', y: 150 },
{ x: '4月', y: 240 },
{ x: '5月', y: 180 },
{ x: '6月', y: 300 }
]
build() {
Column() {
Text('月度数据趋势图')
.fontSize(18)
.margin({ top: 50 })
Canvas(this.canvas)
.width('90%')
.height(300)
.onReady(() => {
//抗锯齿的设置
this.canvas.imageSmoothingEnabled = true
this.canvas.imageSmoothingQuality = 'medium'
this.onCanvasDraw(this.canvas)
})
}
.width('100%')
.padding(10)
}
// 绘制逻辑
private onCanvasDraw(canvas: CanvasRenderingContext2D) {
if (this.canvasWidth === 0 || this.canvasHeight === 0) {
return
}
// 清除画布
canvas.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
// 计算绘图区域
const plotWidth = this.canvasWidth - this.left - this.right
const plotHeight = this.canvasHeight - this.top - this.bottom
// 绘制坐标轴
this.drawAxis(canvas, plotWidth, plotHeight)
// 绘制网格线
this.drawGrid(canvas, plotWidth, plotHeight)
// 绘制折线
this.drawLine(canvas, plotWidth, plotHeight)
// 绘制数据点
this.drawDataPoints(canvas, plotWidth, plotHeight)
// 绘制数据标签
this.drawDataLabels(canvas, plotWidth, plotHeight)
}
// 绘制坐标轴
private drawAxis(canvas: CanvasRenderingContext2D, plotWidth: number, plotHeight: number) {
canvas.beginPath()
// X轴
canvas.moveTo(this.left, this.top + plotHeight)
canvas.lineTo(this.left + plotWidth, this.top + plotHeight)
// Y轴
canvas.moveTo(this.left, this.top)
canvas.lineTo(this.left, this.top + plotHeight)
canvas.strokeStyle = '#666'
canvas.stroke()
}
// 绘制网格线
private drawGrid(canvas: CanvasRenderingContext2D, plotWidth: number, plotHeight: number) {
const xStep = plotWidth / (this.data.length - 1)
const yStep = plotHeight / 4 // 4条水平线
canvas.strokeStyle = '#eee'
canvas.lineWidth = 1
// 垂直线
for (let i = 0; i < this.data.length; i++) {
const x = this.left + i * xStep
canvas.beginPath()
canvas.moveTo(x, this.top)
canvas.lineTo(x, this.top + plotHeight)
canvas.stroke()
}
// 水平线
for (let i = 0; i <= 4; i++) {
const y = this.top + i * yStep
canvas.beginPath()
canvas.moveTo(this.left, y)
canvas.lineTo(this.left + plotWidth, y)
canvas.stroke()
}
}
// 绘制折线
private drawLine(canvas: CanvasRenderingContext2D, plotWidth: number, plotHeight: number) {
const xStep = plotWidth / (this.data.length - 1)
const maxValue = Math.max(...this.data.map(item => item.y))
const valueRatio = plotHeight / maxValue
canvas.beginPath()
this.data.forEach((item, index) => {
const x = this.left + index * xStep
// Y轴坐标需要反转(因为Canvas原点在左上角)
const y = this.top + plotHeight - item.y * valueRatio
if (index === 0) {
canvas.moveTo(x, y)
} else {
canvas.lineTo(x, y)
}
})
canvas.strokeStyle = '#007aff'
canvas.lineWidth = 2
canvas.stroke()
}
// 绘制数据点
private drawDataPoints(canvas: CanvasRenderingContext2D, plotWidth: number, plotHeight: number) {
const xStep = plotWidth / (this.data.length - 1)
const maxValue = Math.max(...this.data.map(item => item.y))
const valueRatio = plotHeight / maxValue
this.data.forEach((item, index) => {
const x = this.left + index * xStep
const y = this.top + plotHeight - item.y * valueRatio
canvas.beginPath()
canvas.arc(x, y, 4, 0, 2 * Math.PI)
canvas.fillStyle = '#007aff'
canvas.fill()
// 白色内圈
canvas.beginPath()
canvas.arc(x, y, 2, 0, 2 * Math.PI)
canvas.fillStyle = '#fff'
canvas.fill()
})
}
// 绘制数据标签
private drawDataLabels(canvas: CanvasRenderingContext2D, plotWidth: number, plotHeight: number) {
const xStep = plotWidth / (this.data.length - 1)
const maxValue = Math.max(...this.data.map(item => item.y))
const valueRatio = plotHeight / maxValue
// X轴标签
this.data.forEach((item, index) => {
const x = this.left + index * xStep
canvas.fillStyle = '#000'
canvas.font = '15vp sans-serif'
canvas.textAlign = 'center'
canvas.fillText(item.x, x, this.top + plotHeight + 20)
})
// Y轴标签
for (let i = 0; i <= 4; i++) {
const value = Math.round(maxValue * (1 - i / 4))
const y = this.top + i * (plotHeight / 4)
canvas.fillStyle = '#000'
canvas.font = '15vp sans-serif'
canvas.textAlign = 'right'
canvas.fillText(value.toString(), this.left - 5, y + 4)
}
}
}