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

react+echarts实现个性化评分展示(类进度条)

需求

示例图
如上图,封装一个组件,通过传入不同的数据展示对应的评分等级:
1-5分处于红色,评差;
6分处于粉色,评中;
7分处于橙色,评良;
8-10分处于绿色,评优秀。

代码

父组件通过接口拿到对应的数据,保存到data中,通过props将数据传给渲染组件。

import React, { useState, useEffect } from 'react';
const ParentCom = () => {const [data, setData] = useState([]);useEffect(() => {// 这里可以做一些接口请求等操作setData([{ value: 9, icon: '', title: 'XXX1' },{ value: 5, icon: '', title: 'XXX2' },{ value: 6, icon: '', title: 'XXX3' },{ value: 7, icon: '', title: 'XXX4' },{ value: 3, icon: '', title: 'XXX5' }]);},[]);return <div><div>{/*页面的其他渲染内容*/}</div><ProgressChartsCom data={data} /></div>
};
export default ParentCom;

在 ProgressChartsCom 组件中进项详细的逻辑处理。

import React, { memo, useEffect, useRef } from 'react';
import { PieChart } from 'echarts/charts';
import { GraphicComponent, LegendComponent, TooltipComponent } from 'echarts/components';
import * as echarts from 'echarts/core';
import { LabelLayout } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import styles from './index.module.less';echarts.use([TooltipComponent,LegendComponent,PieChart,CanvasRenderer,LabelLayout,GraphicComponent,
]);interface IAnnulus {value: number;icon: string;
}const ProgressChartsCom = ({ data }: { data: IAnnulus[] }) => {/*** 根据分数获取对应评价* @param value 进度条数值* @returns 评价*/const getGard = (value) => {if (value <= 5) return '差';if (value === 6) return '中';if (value === 7) return '良';if (value >= 8 && value <= 10) return '优秀';return '未知';};return <div style={{ display: 'flex', justifyContent: 'space-around', height: '200px' }}>{data.map((item, index) => <div style={{ width: `${100 / data.length}%` }} className={styles.progressWrap} key={index}><RenderHandler value={item.value} /><div className={styles.progressCard}><p>{item.value} {getGard(item.value)}</p><img src={item.icon} alt="" /></div></div>)}</div>;
};export default memo(ProgressChartsCom);const RenderHandler = ({ value = 9 }) => {// 创建一个ref,用于存储图表的DOM元素const chartRef = useRef(null);useEffect(() => {initChart(chartRef, value);const resizeHandler = () => {const instance = echarts.getInstanceByDom(chartRef.current);instance.dispose();initChart(chartRef, value);};window.addEventListener('resize', resizeHandler);return () => {window.removeEventListener('resize', resizeHandler);// 销毁实例const instance = echarts.getInstanceByDom(chartRef.current);instance.dispose();};}, []);/*** 获取所有坐标* @param wid 画布宽度* @param hei 画布高度* @param totalData 点位数量* @returns 1-10点位坐标*/function calculatePoints(wid, hei, totalData) {const canvasWidth = wid; // 假设canvas宽度为600const canvasHeight = hei; // 假设canvas高度为400const centerX = canvasWidth / 2;const centerY = canvasHeight / 2;const radius = Math.min(centerX, centerY) * 0.9; // 取半径为较小边的一半的85%作为半径,乘以0.9是因为有radius: ['85%', '95%']设置const totalAngle = 180; // 总角度const anglePerSlice = totalAngle / totalData; // 每份的角度const points = [];for (let i = 0; i < totalData; i++) {const startAngle = (i * anglePerSlice - 180) * Math.PI / 180; // 转换为弧度并减去90度(因为在饼图中通常是从x轴正方向开始计算的)const endAngle = ((i + 1) * anglePerSlice - 180) * Math.PI / 180;const midAngle = (startAngle + endAngle) / 2; // 中间角度const x = centerX + Math.cos(midAngle) * radius; // x坐标const y = centerY + Math.sin(midAngle) * radius; // y坐标points.push({ x, y });};return points;};const getPointerColor = (val) => {if (val <= 5) return 'rgba(230, 81, 81, 1)';if (val === 6) return 'rgba(230, 81, 81, 0.7)';if (val === 7) return 'rgba(253, 171, 57, 1)';if (val >= 8 && val <= 10) return 'rgba(88, 187, 93, 1)';return 'gray';};/*** 初始化图表* @param ref 图表容器* @param value 分值*/const initChart = (ref, value) => {if (!ref.current) return; // 关键:DOM 存在再初始化const wid = ref.current.offsetWidth;const hei = ref.current.offsetHeight;const points = calculatePoints(wid, hei, 10);if (value < 1 || value > points.length) return; // 验证数据有效性const myChart = echarts.init(ref.current);const option = {animation: false,series: [{type: 'pie',radius: ['85%', '95%'],center: ['50%', '50%'],startAngle: 180,endAngle: 0,itemStyle: {borderRadius: 10,borderColor: '#fff',borderWidth: 2,},labelLine: { show: false },label: { show: false },data: [{ value: 5, name: '红', itemStyle: { color: 'rgba(230, 81, 81, 1)' } },{ value: 1, name: '粉', itemStyle: { color: 'rgba(230, 81, 81, 0.7)' } },{ value: 1, name: '橙', itemStyle: { color: 'rgba(253, 171, 57, 1)' } },{ value: 3, name: '绿', itemStyle: { color: 'rgba(88, 187, 93, 1)' } },], // 控制进度条样式数据emphasis: { disabled: true },markPoint: {symbol: 'circle',symbolSize: 17,data: [{name: 'pointer',coord: [0, 0], // 临时值itemStyle: { color: getPointerColor(value) },},],},title: {show: true,},},],graphic: [{type: 'circle',shape: {cx: points[value - 1]?.x ?? 0, // 圆心 x 坐标cy: points[value - 1]?.y ?? 0, // 圆心 y 坐标r: 6, // 圆的半径},style: {stroke: getPointerColor(value), // 边框颜色lineWidth: 3, // 边框宽度fill: '#fff', // 填充颜色为透明,实现空心效果},zlevel: 1000, // 设置 zlevel 以确保圆圈在最上层},], // 自定义图形元素(用于表示当前分数对应位置)};// 先渲染基础图表myChart.setOption(option);};return <div ref={chartRef} className={styles.progress}></div>;
};
.chartsWrap {width: 100%;height: 100%;
}.progress {width: 100%;height: 100%;margin-top: 50px;
}.progressWrap {height: 100%;position: relative;
}.progressCard {position: absolute;top: 60%;left: 50%;transform: translate(-50%, 0);display: flex;flex-direction: column;align-items: center;justify-content: center;p {font-weight: 600;font-size: 18px;line-height: 24px;}img {width: 24px;height: 24px;margin-top: 10px;user-select: none;}
}

思路

其实就是用echarts先绘制一个饼图180度的环状饼图作为等级进度条。
环状饼图
再通过计算获取到圆环上平均10等份的点位坐标,在1-10分对应的点位利用canvas画一个空心圆。
基于上面代码可以更改graphic逻辑,查看十个点位(points)是否正确获取,同时可以拉伸页面视口看计算逻辑是否可以做到自适应页面宽度。

graphic: points.map(item => {return {type: 'circle',shape: {cx: item.x, // 圆心 x 坐标cy: item.y, // 圆心 y 坐标r: 6, // 圆的半径},style: {stroke: getPointerColor(value), // 边框颜色lineWidth: 3, // 边框宽度fill: '#fff', // 填充颜色为透明,实现空心效果},zlevel: 1000,}})

十个点位对应位置

http://www.dtcms.com/a/320441.html

相关文章:

  • Spark01-初识Spark
  • React+TypeScript代码注释规范指南
  • 前端开发(HTML,CSS,VUE,JS)从入门到精通!第八天(Vue框架及其安装)(完结篇) 重点 ! ! !
  • React SSR 水合问题
  • Spark在什么情况下CBO才会判断失误,如何避免
  • 零成本建站:将 Windows 电脑变身为个人网站服务器
  • ubuntu alias命令使用详解
  • AI赋能SEO关键词优化策略
  • 润乾报表、帆软报表的开源替代品—JimuReport(积木报表)
  • 从大数据视角理解时序数据库选型:为何选择 Apache IoTDB?
  • 【Mybatis入门】配置Mybatis(IDEA)
  • OpenAI 开源模型 GPT-OSS MCP服务器深度解密:从工具集成到系统提示全自动化,浏览器+Python无缝协同的底层逻辑
  • 服务器快照与备份的本质区别及正确使用指南 (2025)
  • 腾讯iOA:数据安全的港湾
  • apiSQL网关调优:释放单节点的最大潜能
  • 运维系统构建
  • 实现一个进程池(精讲)
  • Java 虚拟机之双亲委派机制
  • 动手学深度学习(pytorch版):第一章节——引言
  • 力扣300:最长递增子序列
  • pytorch入门3:使用pytorch进行多输出手写数据集模型预测
  • 2025 年最佳no-code和open-source AI Agents
  • java - 深拷贝 浅拷贝
  • 对比学习(Contrastive Learning)面试基础
  • Python 深入浅出装饰器
  • 2026计算机毕业设计选题推荐:如何通过项目实用性来选择创新且高通过率的课题
  • Dify-16: 开发环境配置
  • 【MySQL】SQL优化
  • Linux Shell为文件添加BOM并自动转换为unix格式
  • C++之队列浅析