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

news2025/2/25 3:28:47

本想使用业界大佬们开源的各种图表库(如: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) }
        } />

        <Button
          type=""
          size='small'
          style={{ fontSize: '13px', marginLeft: '7px'  }}
          onClick={
            () => { handleQueryOnClick() }
          }>
          查询数据
        </Button>
      </div>

      <DiyBarChart
        ref={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 = 0
      for (let vo of dataList) {
        barChartParams.totalNum += vo.num
      }

      // 3、设置刻度大小
      if (isOpenPercentage) {
        barChartParams.scaleSize = 100 // 若启用百分比刻度,则刻度大小为100
      } else {
        barChartParams.scaleSize = 0 // 若禁用百分比刻度,则刻度大小为数据列表中,最大数值的最接近的十倍数,且这个十倍数大于最大数值
        let maxSum = 0
        for (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.scaleGap
      for (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 = height
          vo.totalNum = barChartParams.totalNum
        } else {
          const height = divideToPercent(vo.num, barChartParams.scaleSize)
          vo.height = height
          vo.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.coloradmin.cn/o/743469.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ElasticSearch入门教程

文章目录 一、Elasticsearch 概述1.1、ElasticSearch是什么&#xff1f;1.2、ElasticSearch的安装 二、ElasticSearch的使用2.1、索引操作2.2、文档操作2.3、映射操作2.4、高级查询操作 一、Elasticsearch 概述 1.1、ElasticSearch是什么&#xff1f; 官网解释如图所示&#…

Rdkit|操作分子对象

github&#xff1a;地址 文章目录 RDKit|操作分子对象引入所需库获取分子中的原子获取原子的坐标信息访问单个原子的信息访问所有原子分子中的键操作获取键的信息 获取分子中所有的环 RDKit|操作分子对象 引入所需库 from rdkit import Chem from rdkit.Chem import Draw获取…

Mysql基本语法+Navicat使用

进入数据库&#xff1a;mysql -uroot -p 修改数据库密码&#xff1a;ALTER USER rootlocalhost IDENTIFIED BY 这里输入密码; &#xff08;如&#xff1a;ALTER USER rootlocalhost IDENTIFIED BY 111111;&#xff09; 创建数据库&#xff1a;create database 数据库名; 查…

刷题记录01

题目一. 这道题要先解释一下什么是非递增,非递增就是a[i] >a[i1],递增则是相反. 非递减就是a[i]>a[i1],递减就是相反 大方向思路是: 遍历数组判断相邻元素的顺序关系统计排序子序列数量 具体思路: 本题依次比较整个数组a[i1]>a[i] &#xff0c;则进入非递增序列判…

在vue中点击弹框给弹框中的表格绑值

场景描述&#xff1a;如下图所示&#xff0c;我们需要点击 ‘账单生成’ 按钮&#xff0c;然后里边要展示一个下图这样的表格。 最主要的是如何展示表格中的内容&#xff0c;一起看看吧&#xff01; <template><!-- 水费 欠费--><el-dialog title"水费欠费…

静态图片转3D动态GIF/视频

Leiapix是一项令人印象深刻的技术&#xff0c;它可以让静态的图片动起来&#xff0c;为观众提供沉浸式和交互式的图像体验。这项创新的技术使用了Leia Inc.的自适应光栅屏幕技术&#xff0c;通过利用人眼的视差和立体视觉效应&#xff0c;将图像中的元素以动态的方式呈现出来&a…

《第一次线下面试总结》

《第一次线下面试总结》 面试时间&#xff1a;2023/7/11 上午10点 面试总时长20分钟。 实习薪资&#xff1a;2.3k…后期看表现&#xff0c;可根据实际情况那啥 。估计是看锤子… 一、HR面 自我介绍你哪里的、目前住哪里等基本信息。你偏向前端还是后端&#xff1f;说说你的项目…

电路分析基础学习(上)第7章

李瀚荪版电分第二版 目录 二阶电路的定义 电路中的等幅振荡与阻尼振荡 RLC电路的零输入响应 ----------------------------------------------------------------------------------------------------------------------------- 二阶电路的定义 二阶电路是指由电容、电感…

[QT编程系列-3]:C++图形用户界面编程,QT框架快速入门培训 - 2- QT程序的运行框架:HelloWorld、常见控件、对象树原理

目录 2. QT程序的运行框架 2.1 Hello World程序框架 2.2 QT Designer初识 2.3 用QT Designer设计用户登录界 2. QT程序的运行框架 2.1 Hello World程序框架 上述示例代码中&#xff0c;首先根据应用程序的需求使用 QCoreApplication 或 QApplication 定义 app 对象。如果你…

[综述] Generative AI meets 3D: A Survey on Text-to-3D in AIGC Era

论文&#xff5c; 改文章是23年5月27日挂在arxiv上&#xff0c;本文重点关注4.1节Text Guided 3D Avatar Generation、4.4节Text Guided 3D Shape Transformation和第5章Discussion Text Guided 3D Avatar Generation DreamAvatar DreamAvatar: Text-and-Shape Guided 3D Hu…

k8s中网络通讯简单介绍

1 前言 Kubernetes的网络模型假定了所有的pod都在一个可以直接连通的扁平的网络空间中&#xff0c;这在GCE&#xff08;Google Compute Engine&#xff09;里面是现成的网络模型&#xff0c;Kubernetes假设这定这个网络已经存在。但是在私有云里搭建Kubernetes集群&#xff0c;…

CHI read trans flow

Read transactions with DMT and without snoops 对于不产生snoop的read trans&#xff0c;建议使用DMT功能&#xff0c;如下图所示&#xff1a; 注意点&#xff1a; a. SNF并不需要给HNF回响应&#xff0c;因为RN发送的compack可以释放HNF处记录的请求; Read transaction wi…

《Redis 核心技术与实战》课程学习笔记(七)

切片集群&#xff1a;数据增多了&#xff0c;是该加内存还是加实例&#xff1f; 切片集群&#xff0c;也叫分片集群&#xff0c;就是指启动多个 Redis 实例组成一个集群&#xff0c;然后按照一定的规则&#xff0c;把收到的数据划分成多份&#xff0c;每一份用一个实例来保存。…

使用Dcoker Registry搭建私有镜像仓库

Dcoker Registry 和Harbor有什么相似和区别&#xff1f;各自有什么优劣&#xff0c;请详细介绍 Docker Registry和Harbor都是容器镜像仓库管理系统&#xff0c;用于存储、管理和分发Docker镜像。它们有一些相似之处&#xff0c;但也存在一些区别。下面是对它们的相似之处和区别…

idea导入springboot项目,下载的pom.xml文件是html格式。

一、可以看到我的pom.xml的parent标签 <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0…

使用cuda报错的一次记录(CUDA error: out of memory)

原因&#xff1a; 由于batch_size设置过大导致的&#xff01;&#xff01;&#xff01;

机器学习技术(四)——特征工程与模型评估

机器学习技术&#xff08;四&#xff09;——特征工程与模型评估(1️⃣) 文章目录 机器学习技术&#xff08;四&#xff09;——特征工程与模型评估(:one:)一、特征工程1、标准化2、特征缩放3、缩放有离群值的数据4、非线性转换5、样本归一化6、特征二值化7、标称特征编码(one-…

亚马逊云科技推出的一项完全托管的生成式AI服务——Amazon Bedrock

在全球生成式AI浪潮兴起之际&#xff0c;以“智联世界&#xff0c;生成未来”为主题的2023世界人工智能大会&#xff08;WAIC 2023&#xff09;于7月6日在上海世博中心拉开帷幕。大会首日&#xff0c;亚马逊云科技携生成式AI产品Amazon Bedrock亮相大会现场&#xff0c;亚马逊云…

C语言 指针进阶(二)

目录 一.函数指针 1.1函数指针的认识 1.2函数指针的使用 二、函数指针数组 1.1函数指针的认识 1.2 函数指针数组实现计算器 三、指向函数指针数组的指针 四、回调函数 通过使用qsort函数加强对回调函数的理解 qsort排序整形 qosrt排序结构体 用冒泡排序的思想&…

Java设计模式之结构型-组合模式(UML类图+案例分析)

目录 一、基础概念 二、UML类图 三、角色分析 四、案例分析 1、基本实现 2、菜单遍历 五、总结 一、基础概念 组合模式&#xff08;Composite Pattern&#xff09;又叫部分-整体模式&#xff0c;它通过将对象组合成树形结构来表示“整体-部分”的层次关系&#xff0c…