使用React的函数式组件实现一个具有过渡变化、刻度切换、点击高亮的柱状图DIY组件

本想使用业界大佬们开源的各种图表库(如:ECharts、G2可视化引擎、BizCharts ...),但是有的需求不仅要求有过渡变化,还要点击某个图高亮同时发送HTTP请求数据等功能,着实不知道怎么把canvas或svg绘制的图表弄成高亮,于是自己动手丰衣足食。虽然说React是通过虚拟DOM来渲染视图的,最好不要直接操作DOM,但是目前技术有限,而且也只是操作一下DOM来修改一点点CSS样式,这个以后再优化吧。

1、首先设计父页面【/src/views/Example/DiyCharts/index.jsx】

import { Button, Switch } from 'antd'
import { useEffect, useState, useRef } from 'react'
import DiyBarChart from './components/diyBarChart'const DiyCharts = () => {// 柱状图引用对象const diyBarChartRef = useRef(null)// 柱状图数据列表const [dataList, setDataList] = useState([])// 是否启用百分比刻度,若启用则显示百分比,若禁用则显示具体数值const [isOpenPercentage, setIsOpenPercentage] = useState(false)/*** 查询事件句柄*/const handleQueryOnClick = function () {diyBarChartRef.current.handleResetBarChar()setTimeout(() => {setDataList([{ num: (Math.floor(Math.random() * 100)), title: '家具家电' },{ num: (Math.floor(Math.random() * 100)), title: '生鲜水果' },{ num: (Math.floor(Math.random() * 100)), title: '粮油副食' },{ num: (Math.floor(Math.random() * 100)), title: '母婴用品' },{ num: (Math.floor(Math.random() * 100)), title: '美容护肤' },{ num: (Math.floor(Math.random() * 100)), title: '清洁卫生' },])}, 1500)}useEffect(() => {handleQueryOnClick()}, [])return (<><div style={{ display: 'flex', alignItems: 'center' }}><span style={{ fontSize: '13px', margin: '7px' }}>是否启用百分比刻度 : </span><Switch checked={isOpenPercentage} onChange={() => { setIsOpenPercentage(!isOpenPercentage) }} /><Buttontype=""size='small'style={{ fontSize: '13px', marginLeft: '7px'  }}onClick={() => { handleQueryOnClick() }}>查询数据</Button></div><DiyBarChartref={diyBarChartRef}width={ '450px' }height={ '300px' }dataList={dataList}isOpenPercentage={isOpenPercentage}onData={(item) => {console.log(item)}} /></>)
}export default DiyCharts

2、然后设计子组件【/src/views/Example/DiyCharts/components/diyBarChart/index.jsx】

import { useState, useEffect, useRef, forwardRef, useImperativeHandle } from 'react'
import { message } from 'antd'
import './style.scss'const DiyBarChart = forwardRef((props, ref) => {const barChartRef = useRef(null)const { width, height, dataList, isOpenPercentage } = props// 柱状图配置参数let barChartParams = {width: width ? width : '600px',height: height ? height : '150px',scaleSize: 0, // 刻度大小scaleGap: 5, // 刻度间隔totalNum: 0, // 数值总数barIdPrefix: 'diy-bar-chart-', // 柱状图li元素的ID前缀,如:diy-bar-chart-0 diy-bar-chart-1 diy-bar-chart-2 diy-bar-chart-3}// 柱状图y轴刻度列表const [y_AxisList, setY_AxisList] = useState([100, 80, 60, 40, 20, 0])// 柱状图x轴数据列表const [x_AxisList, setX_AxisList] = useState([{ 'num': 0, title: '家具家电', height: '0%', totalNum: 1 },{ 'num': 0, title: '生鲜水果', height: '0%', totalNum: 1 },{ 'num': 0, title: '粮油副食', height: '0%', totalNum: 1 },{ 'num': 0, title: '母婴用品', height: '0%', totalNum: 1 },{ 'num': 0, title: '美容护肤', height: '0%', totalNum: 1 },{ 'num': 0, title: '清洁卫生', height: '0%', totalNum: 1 },])/*** 两数相除结果转为百分数*/const divideToPercent = (num1, num2) => {return (Math.round(num1 / num2 * 10000) / 100.00 + '%')}/*** 获取一个数且大于它,以及与它最接近的十倍数*/const getNearestTen = (num) => {return Math.ceil(num/10) * 10}/*** 构建柱状图数据*/const handleInitBarChart = async (dataList) => {if (dataList.length == 0) {return}try {console.log('dataList =>', dataList)// 2、设置数值总数barChartParams.totalNum = 0for (let vo of dataList) {barChartParams.totalNum += vo.num}// 3、设置刻度大小if (isOpenPercentage) {barChartParams.scaleSize = 100 // 若启用百分比刻度,则刻度大小为100} else {barChartParams.scaleSize = 0 // 若禁用百分比刻度,则刻度大小为数据列表中,最大数值的最接近的十倍数,且这个十倍数大于最大数值let maxSum = 0for (let vo of dataList) {if (vo.num > maxSum) {maxSum = vo.num}}barChartParams.scaleSize = getNearestTen(maxSum)}// 4、设置柱状图y轴刻度列表const tempY_AxisList = []const degree = barChartParams.scaleSize / barChartParams.scaleGapfor (let i = 0; i <= barChartParams.scaleGap; i++) {tempY_AxisList.push(parseInt(i * degree))}tempY_AxisList.sort((a, b) => {return b - a // 倒序})setY_AxisList(tempY_AxisList)// console.log('tempY_AxisList =>', tempY_AxisList)// 5、设置柱状图x轴数据列表const tempX_AxisList = []for (let vo of dataList) {if (isOpenPercentage) {const height = divideToPercent(vo.num, barChartParams.totalNum)vo.height = heightvo.totalNum = barChartParams.totalNum} else {const height = divideToPercent(vo.num, barChartParams.scaleSize)vo.height = heightvo.totalNum = barChartParams.totalNum}tempX_AxisList.push(vo)}setX_AxisList(tempX_AxisList)// console.log('tempX_AxisList =>', tempX_AxisList)} catch (e) {console.error(e)}}/*** 柱状图点击事件句柄方法*/const handleBarChartOnClick = async (evt, item, index, length) => {console.log('handleBarChartOnClick =>', evt, item, index, length)message.info(JSON.stringify(item), 1)const current = await barChartRef.current// console.log('barChartRef.current =>', current)for (let i = 0; i < length; i++) {const li = document.getElementById(barChartParams.barIdPrefix + i)li.querySelector('div').style.backgroundColor = 'transparent'}const li = document.getElementById(barChartParams.barIdPrefix + index)li.querySelector('div').style.backgroundColor = 'rgba(199, 220, 255, 0.8)'props.onData(item) // 子组件传参给父页面}const handleResetBarChar = () => {setX_AxisList([{ 'num': 0, title: '家具家电', height: '0%', totalNum: 1 },{ 'num': 0, title: '生鲜水果', height: '0%', totalNum: 1 },{ 'num': 0, title: '粮油副食', height: '0%', totalNum: 1 },{ 'num': 0, title: '母婴用品', height: '0%', totalNum: 1 },{ 'num': 0, title: '美容护肤', height: '0%', totalNum: 1 },{ 'num': 0, title: '清洁卫生', height: '0%', totalNum: 1 },])}/*** 将子组件的方法暴露给父组件调用*/useImperativeHandle(ref, () => ({handleResetBarChar}))useEffect(() => {console.log('dataList =>', dataList)handleInitBarChart(dataList)}, [dataList, isOpenPercentage])return (<>{/* ^ 柱状图 */}<div ref={barChartRef} className="diy-bar-chart" style={{ width: barChartParams.width, height: barChartParams.height }}><div className="diy-bar-chart__container"><div className="__y-axis" /><ul className="__y-ul">{y_AxisList.map((item, index) => {return (<li key={index}>{isOpenPercentage? <span><label>{ item + '%'}</label></span>: <span><label>{ item }</label></span>}</li>)})}</ul><ul className="__x-ul">{x_AxisList.map((item, index) => {return (<li id={barChartParams.barIdPrefix + index} key={index} onClick={evt => handleBarChartOnClick(evt, item, index, x_AxisList.length)}>{<div className="__bar-outer"><div className="__bar-inner" style={{ height: item.height }} data-height={ item.height }><p><span>{ item.num }</span><small>({ divideToPercent(item.num, item.totalNum) })</small></p></div><label>{ item.title }</label></div>}</li>)})}</ul></div></div>{/* / 柱状图 */}</>)
})export default DiyBarChart

3、最后加点柱状图样式【/src/views/Example/DiyCharts/components/diyBarChart/style.scss】

.diy-bar-chart {position: relative;display: table;padding: 35px 0 25px 50px;transition: all ease 0.3s;.diy-bar-chart__container {position: relative;display: flex;flex-direction: row;width: 100%;height: 100%;margin: 0 auto;.__y-axis {position: absolute;bottom: 0;width: 1px;height: calc(100% + 35px);border-left: 1px solid #ddd;}.__y-ul {position: absolute;display: flex;flex-direction: column;width: 100%;height: 100%;margin: 0;padding: 0;li {position: relative;bottom: 0;flex: 1;display: flex;border-top: 1px solid #ddd;list-style: none;span {position: absolute;bottom: 0;left: -45px;top: -50%;display: block;width: 35px;height: 100%;text-align: right;label {position: absolute;display: grid;width: 100%;height: 100%;align-items: center;font-size: 13px;text-align: right;color: #686868;}}}li:last-child {flex: 0;span {top: -6.5px;}}&:before {position: relative;bottom: 35px;font-size: 13px;color: #5e7ce0;border-left: 1px solid #f00;}}.__x-ul {display: flex;width: 100%;height: 100%;margin: 0;padding: 0 10px;li {display: table-cell;flex: 1;height: 100%;      text-align: center;position: relative;.__bar-outer {position: relative;width: 100%;height: 100%;transition: all ease 0.3s;cursor: pointer;.__bar-inner {position: absolute;bottom: 0;left: 0;right: 0;display: block;margin: 0 auto;width: 20px;height: 0;background-color: #5e7ce0;transition: all ease-in-out 0.3s;text-align: center;p {position: relative;left: 0;bottom: 32px;width: 100px;height: 100%;transform: translateX(-40px);margin: 0;font-size: 13px;color: #5e7ce0;text-align: center;span {display: block;font-size: 14px;line-height: 14px;}small {font-size: 12px;line-height: 12px;color: #686868;}}}label {position: absolute;left: 0;bottom: -25px;width: 100%;text-align: center;font-size: 13px;color: #686868;}&:hover {background-color: rgb(231, 240, 255, 0.8) !important;}}}li:first-child {.__bar-outer {background-color: rgba(199, 220, 255, 0.8);}}}}
}

4、效果如下:~

​​​​​​​

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/20952.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【OJ比赛日历】快周末了,不来一场比赛吗? #07.15-07.21 #7场

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2023-07-15&#xff08;周六&#xff09; #3场比赛2023-07-16…

链上衍生品协议 ProsperEx:探索 RWA 叙事,打造 DeFi 变异黑马

“ProsperEx 本身背靠着具备潜力的链上衍生品赛道&#xff0c;同时也是 RWA 领域早期的布局者之一&#xff0c;其有望成为 Web2 世界与 Web3 世界的早期连接点&#xff0c;并具备广泛且规模性捕获用户的能力。” 从2020年DeFi之夏链上世界迎来了爆发后&#xff0c;链上衍生品赛…

Gateway网关组件(在Spring Cloud整合Gateway(idea19版本))

Spring Cloud Gateway官网:Spring Cloud Gateway 局域网中就有网关这个概念&#xff0c;局域网接收数据或发送数据都要通过网关&#xff0c;比如使用VMware虚拟机软件搭建虚拟机集群的时候&#xff0c;往往我们需要选择IP段中的⼀个IP作为网关地址,网关可以对请求进行控制,提升…

JAVA+Selenium最简单的处理登录弹窗的方式

在做自动化测试遇到需要处理登录弹窗情况&#xff0c;例如我的用户名为admin, 密码为admin, 那么想要登录http://10.10.168.1, 只需要使用以下链接访问即可立即登录, 并免除弹窗: http://账号:密码网址

Layui 简单介绍及入门

目录 一.Layui &#xff08;国产品牌&#xff09; 1.1 Layui是什么 二.比较layui和easyui&#xff0c;bootstrap的区别 2.1 layui和bootstrap的对比 2.2 layui和easyui对比 三.Layui入门 四.案例 一.Layui &#xff08;国产品牌&#xff09; 1.1 Layui是什么 用我的话来…

MySQL每日一练:多表查询——连接查询、子查询

目录 1、首先创建员工表emp和部门表dept&#xff1a; dept表&#xff1a; emp表&#xff1a; 2、插入数据&#xff1a; dept表&#xff1a; emp表&#xff1a; 3、 按条件查找 1、首先创建员工表emp和部门表dept&#xff1a; dept表&#xff1a; create table dept (…

【MQ】Windows上RabbitMQ的安装与启动

文章目录 下载Erlang安装RabbitMQ 下载Erlang RabbitMQ基于Erlang语言&#xff0c;因此使用RabbitMQ之前需要先安装Erlang&#xff0c;如下 Erlang语言下载 这里我是用的是25.2.2这个版本&#xff0c;我的机器是64bit的&#xff0c;所以下win64的即可。 下载完毕安装包之后点…

2023年软件测试岗位将会越来越少吗?

我的整体意见是测试岗位不会变少&#xff0c;反而相对于其他岗位会变的更重要一些。 首先纠正一个非常非常错误的观念。测试和测试开发是两个岗位&#xff1f;No&#xff0c;不是的。测试开发是属于测试的。 测试开发只不过是使用类似于开发的技术和能力&#xff0c;来达到测试…

【接口/性能测试】Jmeter引用 jar包的三种方式(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 实现对登录密码进…

idea-spring boot开发

安装maven与配置配置maven安装插件 已经装好了idea与jdk 安装maven与配置 下载地址: https://maven.apache.org/download.cgi 下载合适的版本 配置maven 打开设置: 直接搜索 :maven 配置变量: 此电脑->属性->高级系统设置->环境变量 新建系统变量 MAVEN_HOME&#xff…

windows提权总结

文章目录 windows基础知识提权总结基础知识提权思路metasplit 提权反弹shell提权windows系统配置错误提权系统服务权限配置错误不带引号的服务路径提权注册键 AlwaysInstallElevated 本地dll劫持提权第三方提权sqlserver提权mysql提权MOF提权G6FTP提权 绕过UAC bypassuac远程终…

使用 TensorRT、卡尔曼滤波器和 SORT 算法进行实时对象检测和跟踪:第 1 部分训练模型

实时物体检测和跟踪在监控、自动驾驶和机器人等各种应用中至关重要。这些任务需要能够实时处理高分辨率视频流的高效算法。近年来,基于深度学习的目标检测算法(例如YOLO、SSD和Faster R-CNN)在图像和视频中的目标检测和定位方面显示出了令人印象深刻的结果。然而,这些算法的…