react?echarts?tree樹圖搜索展開功能示例詳解
前言
umi+antd-admin 框架中使用類組件+antd結合echarts完成樹圖數(shù)據(jù)展示和搜索展開功能
最終效果
版本信息
"antd": "3.24.2",
"umi": "^2.7.7",
"echarts": "^4.4.0",
"echarts-for-react": "^2.0.15-beta.1",
核心功能:
- 左右樹圖數(shù)據(jù)展示,并且基本的展開功能
- 左樹圖的搜索功能,搜索后結果自動展開默認顯示上級
- 點擊左樹圖,右邊樹圖對應節(jié)點展開
- 右樹圖背景水印,全屏功能
- 右樹圖定制節(jié)點樣式
關鍵思路:
點擊左邊樹狀結構時,首先通過遞歸找到具體哪一個分支中包含對應結果,然后再通過遞歸一層一層的添加屬性collapsed,如果改分支下面包含結果添加collapsed:false,其他分支添加collapsed:true
重新渲染時echarts tree的options中initialTreeDepth屬性需要設置一下,為結果點所在層級
附上代碼
數(shù)據(jù)data.js
export const lineLabel = { //樹圖中部分節(jié)點展示樣式為線上樣式 fontSize: 14, color: '#333333', offset: [ 5, -15], borderRadius: 0, borderColor: 'transparent', backgroundColor: 'transparent', position: 'left', verticalAlign: 'middle', align: 'right', } // 數(shù)據(jù) export const data = [ { yybid: '1', fid: '0', grade: '0', yybmc: '課程',itemStyle: { color: "#DE4A3C" } }, { yybid: '101', fid: '1', grade: '1', yybmc: '語文',itemStyle: { color: "#DE4A3C" } }, { yybid: '1011', fid: '101', grade: '2', yybmc: '聽寫',itemStyle: { color: "#DE4A3C" } }, { yybid: '10111', fid: '1011', grade: '3', yybmc: '生字',itemStyle: { color: "#DE4A3C" }}, { yybid: '10111-1', fid: '10111', grade: '4', yybmc: '文字',itemStyle: { color: "#DE4A3C" },label:lineLabel }, // 比如這個就是顯示在線上的意思,可以定制節(jié)點樣式 { yybid: '10111-1-1', fid: '10111-1', grade: '5', yybmc: '同音字',itemStyle: { color: "#DE4A3C" } }, { yybid: '10111-1-1-1', fid: '10111-1-1', grade: '6', yybmc: '...',itemStyle: { color: "#FFFFFF" } }, { yybid: '10111-1-1-2', fid: '10111-1-1', grade: '6', yybmc: '...',itemStyle: { color: "#FFFFFF" } }, { yybid: '10111-1-1-3', fid: '10111-1-1', grade: '6', yybmc: '...',itemStyle: { color: "#FFFFFF" } }, { yybid: '10111-1-1-4', fid: '10111-1-1', grade: '6', yybmc: '...',itemStyle: { color: "#FFFFFF" } }, { yybid: '10111-1-2', fid: '10111-1', grade: '5', yybmc: '多音字',itemStyle: { color: "#DE4A3C" } }, { yybid: '102', fid: '1', grade: '1', yybmc: '數(shù)學',itemStyle: { color: "#DE4A3C" } }, ..... ]
功能:
import React from 'react'; import { Row, Col, Input, Tree, Button, Icon } from 'antd'; import TreeUtils from '../../../../../utils/treeUtils'; import ReactEcharts from 'echarts-for-react' import styles from './index.less'; import { data } from './data' const { Search } = Input; class Map extends React.Component { constructor(props) { super(props); this.state = { cNameTreeData: [], cNameList: [], expandedKeys: [], // 展開節(jié)點key autoExpandParent: true, cmapOpt:{}, cmapTreeData:{}, cmapTreeDataOrigin:{}, defaultExpandedKeys:[], // 默認展開節(jié)點key keyWord:'',//搜索關鍵字 isFullScreen:false, }; } componentDidMount() { this.fetchcName(); } getOption =(data,initialTreeDepth)=>{ // 設置水印 let user = { name :'管理員' , loginName :'admin'} const waterMarkText = `${user.name} ${user.loginName}` const canvas = document.createElement('canvas') canvas.width = 200 canvas.height = 150 const ctx = canvas.getContext('2d') ctx.textAlign = 'center' ctx.textBaseline = 'middle' ctx.globalAlpha = 0.09 ctx.font = '16px sans-serif' ctx.translate(70,90) ctx.rotate(-Math.PI / 4) ctx.fillText(waterMarkText, 0, 0) const opt= { backgroundColor: { image:canvas }, toolbox: { top:30, right:30, itemSize:24, feature: { //自定義toolbox,必須以my開頭,全屏功能 myFullScreen:{ show:true, title:this.state.isFullScreen?'退出全屏':'全屏', icon:"image://" + require("../../../../../assets/fullScreen.png"), emphasis: { iconStyle: { textFill: '#DE4A3C', //文本顏色,若未設定,則依次取圖標 emphasis 時的填充色、描邊色,若都不存在,則為'#000' textAlign: 'center', //文本對齊方式,屬性值:left/center/right } }, onclick: (e) => { //isFullScreen定義在data中,初始化為false this.setState({ isFullScreen:!this.state.isFullScreen },()=>{ const element = document.getElementById('cmapTree'); if (element.requestFullScreen) { // HTML W3C 提議 element.requestFullScreen(); } else if (element.msRequestFullscreen) { // IE11 element.msRequestFullScreen(); } else if (element.webkitRequestFullScreen) { // Webkit (works in Safari5.1 and Chrome 15) element.webkitRequestFullScreen(); } else if (element.mozRequestFullScreen) { // Firefox (works in nightly) element.mozRequestFullScreen(); } // 退出全屏 if (element.requestFullScreen) { document.exitFullscreen(); } else if (element.msRequestFullScreen) { document.msExitFullscreen(); } else if (element.webkitRequestFullScreen) { document.webkitCancelFullScreen(); } else if (element.mozRequestFullScreen) { document.mozCancelFullScreen(); } }) } } } }, series: [ { type: 'tree', // silent:true, data: [data], top: '10%', left: '10%', bottom: '10%', right: '15%', // edgeShape:'polyline', symbolSize: 10, // symbolSize: [30, 30], label: { color: '#FFFFFF', distance: 0, fontSize: 16, borderWidth: 0, borderRadius: 4, borderColor: 'rgba(222, 74, 60, 0.9)', backgroundColor: 'rgba(222, 74, 60, 0.9)', padding: [6, 10, 6 ,10], // 最開始的樣式 // padding: [6, 10], position: 'left', verticalAlign: 'middle', align: 'right', // 最開始的樣式 // align: 'insideRight', }, leaves: { label: { position: 'right', // 最開始的樣式 // position: 'left', verticalAlign: 'middle', align: 'left', color: '#333333', distance: -15, margin: 0, fontSize: 16, borderWidth: 0, borderColor: 'rgba(222, 74, 60, 0.1)', backgroundColor: 'rgba(222, 74, 60, 0.1)', borderRadius: 4, padding: [6, 10, 6 , 20], // 最開始的樣式 // padding: [6, 10], } }, itemStyle:{ borderType : 'solid', borderWidth : 2, borderColor : '#DE4A3C', }, lineStyle:{ color:'#DE4A3C' }, edgeForkPosition: "72%", emphasis: { // 高亮 focus: 'descendant' }, animationDuration: 300, animationDurationUpdate: 300, initialTreeDepth:initialTreeDepth, // 樹圖初始展開層級,樹圖初始展開的層級(深度)。根節(jié)點是第 0 層,然后是第 1 層、第 2 層,... ,直到葉子節(jié)點 roam:true,//鼠標縮放,拖拽整顆樹 expandAndCollapse: true,//無關的子樹折疊收起 // width: "50%"http://組件寬度 } ] } this.setState({ cmapOpt:opt }) } // 獲取名稱 fetchcName = () => { const cName = {}; // 設置樹數(shù)據(jù) const datas = TreeUtils.toTreeData(data, { keyName: 'yybid', pKeyName: 'fid', titleName: 'yybmc', normalizeTitleName: 'title', normalizeKeyName: 'key' }, true); cName.datas = []; datas.forEach((item) => { const { children } = item; cName.datas.push(...children); }); cName.dataLoaded = true; // 設置樹形圖數(shù)據(jù) const optData = TreeUtils.toTreeMapData(data, { keyName: 'yybid', pKeyName: 'fid', titleName: 'yybmc', normalizeTitleName: 'name', normalizeKeyName: 'value' }, true); cName.optDatas = []; optData.forEach((item) => { const { children } = item; cName.optDatas.push(...children); }); // 設置默認展開第一層 const expandedKeys = [] cName.datas.forEach(item =>{ expandedKeys.push(item.key) }) this.setState({ cNameTreeData: cName.datas, cNameList: data, cmapTreeData:cName.optDatas[0], cmapTreeDataOrigin:cName.optDatas[0], expandedKeys, },()=>{ this.getOption(cName.optDatas[0],1) }); } // 關鍵字搜索 handleOnkeyWord = (e) => { const keyWord = e.target.value; this.setState({ keyWord:keyWord.trim() }) } // 搜索功能 searchTree = () =>{ if(this.state.keyWord){ this.getTreeExpand(this.state.keyWord) }else { // 設置默認展開第一層 const cName = {}; const datas = TreeUtils.toTreeData(data, { keyName: 'yybid', pKeyName: 'fid', titleName: 'yybmc', normalizeTitleName: 'title', normalizeKeyName: 'key' }, true); cName.datas = []; datas.forEach((item) => { const { children } = item; cName.datas.push(...children); }); const expandedKeys = [] cName.datas.forEach(item =>{ expandedKeys.push(item.key) }) this.setState({ cNameTreeData: cName.datas, expandedKeys, }); } } // 設置左樹展開 getTreeExpand = (keyWord) =>{ // 篩選數(shù)據(jù) const { cNameList } = this.state; const newTreeList = cNameList.filter((item) => { if (item.yybmc.indexOf(keyWord) !== -1) { return true; } return false; }); const newTreeParent = []; const expandedKeys = []; // 獲取所有子節(jié)點的父節(jié)點 newTreeList.forEach((item) => { expandedKeys.push(item.yybid); // 不是根節(jié)點 newTreeParent.push(item); for (let i = item.grade; i > 0; i--) { const newParent = this.getByChildId(newTreeParent[newTreeParent.length - 1].fid); newTreeParent.push(newParent[0]); } }); // 合并數(shù)組 const tempNewData = [...newTreeParent, ...newTreeList]; // 數(shù)組去重 let newData = new Set(tempNewData); newData = [...newData]; // 構造樹形數(shù)據(jù) const newTreeData = []; const datas = TreeUtils.toTreeData(newData, { keyName: 'yybid', pKeyName: 'fid', titleName: 'yybmc', normalizeTitleName: 'title', normalizeKeyName: 'key' }, true); newTreeData.datas = []; datas.forEach((item) => { const { children } = item; newTreeData.datas.push(...children); }); newTreeData.dataLoaded = true; this.setState({ cNameTreeData: newTreeData.datas, expandedKeys, autoExpandParent: true, }); } // 根據(jù)子節(jié)點找到父節(jié)點 getByChildId(childId) { return this.state.cNameList.filter((item) => { return item.yybid === childId; }); } // 選中樹形數(shù)據(jù) onSelect = (selectedKeys) => { const select = this.state.cNameList.filter((item) => { return item.yybid === selectedKeys[0]; }); if (select && select.length > 0) { this.handleOnExpand(select[0]) } } onExpand = (expandedKeys) => { this.setState({ expandedKeys, autoExpandParent: false, }); }; // 處理data中的children添加屬性展開顯示 addCollapsed = (children, selectItem) => { let {yybid} = selectItem; const newChildren = [] children.forEach(obj =>{ let newObj = {} // newObj = {...obj,collapsed :true} if(obj.value === yybid){ newObj = {...obj,collapsed :false} }else if(obj.children && obj.children.length) { if(!this.isHaveChildren(obj.children,yybid)){ let newChildren = {} newChildren = this.addCollapsed(obj.children,selectItem) newObj = {...obj,collapsed :true,children:newChildren} }else{ let newChildren = {} newChildren = this.addCollapsed(obj.children,selectItem) newObj = {...obj,collapsed :false,children:newChildren} } }else { newObj = {...obj,collapsed :true} } newChildren.push({...obj,...newObj}) }) return newChildren } // 判斷下面是否有子節(jié)點 isHaveChildren = (arr,selectId) =>{ const res = [] arr.forEach(item =>{ if(item.value === selectId){ res.push('true') }else if(item.children && item.children.length) { res.push(String(this.isHaveChildren(item.children,selectId))) }else { res.push('false') } }) return res.some(resObj => resObj === 'true') } // 樹狀圖搜索展開 handleOnExpand = (selectItem) =>{ const { grade } = selectItem const { children } = this.state.cmapTreeDataOrigin let newChildren = [] if(grade === '0'){ // 設置樹形圖數(shù)據(jù) const optData = TreeUtils.toTreeMapData(data, { keyName: 'yybid', pKeyName: 'fid', titleName: 'yybmc', normalizeTitleName: 'name', normalizeKeyName: 'value' }, true); let optDatas = []; optData.forEach((item) => { const { children } = item; optDatas.push(...children); }); return this.setState({ cmapTreeData:optDatas[0] },()=>{ this.getOption(optDatas[0],'1') }) }else if(grade === '1'){ children.forEach(obj =>{ let newObj = {} if(obj.value === selectItem.yybid){ newObj = {...obj,collapsed :false} }else { newObj = {...obj,collapsed :true} } newChildren.push({...obj,...newObj}) }) }else { newChildren = this.addCollapsed(children, selectItem) // console.log(selectItem) } this.setState({ cmapTreeData:{ ...this.state.cmapTreeData, children:newChildren } },()=>{ this.getOption(this.state.cmapTreeData,grade) }) } componentWillUnmount = () => { this.setState = (state,callback)=>{ return; }; } render() { const { cNameTreeData, autoExpandParent, expandedKeys,cmapOpt } = this.state; return ( <React.Fragment> <Row style={{height:'88vh'}}> <Col xs={5} sm={5} lg={5} xl={5} style={{height:'100%'}}> <div style={{width:'100%',display:'flex',height:'100%'}}> <div style={{height:'100%',width:'90%',padding:'16px 20px 0px 20px'}}> {/* 搜索框 */} <div style={{fontSize:'16px',marginBottom:'10px',color:'#666666',fontWeight:'bold'}}>搜索功能</div> <Search allowClear style={{ width: '100%',height:'40px', margin: '0px 10px 10px 0px',borderColor:'#999' }} placeholder="請輸入" onChange={e => this.handleOnkeyWord(e)} onPressEnter={this.searchTree} onSearch={this.searchTree} /> {/* 樹形組件 */} <Tree style={{height:'85%',overflowY:'auto',width:'100%'}} className={styles.tree} onSelect={this.onSelect} onExpand={this.onExpand} expandedKeys={expandedKeys} autoExpandParent={autoExpandParent} treeData={cNameTreeData} height={800} /> </div> </div> </Col> {/* tree圖 */} {cmapOpt? <Col xs={19} sm={19} lg={19} xl={19} style={{height:'100%'}}> {/* <div style={{position:'absolute',top:'35px',right:'30px',fontSize:'16px',color:'#DE4A3C'}}>全屏</div> */} <div id="cmapTree" style={{backgroundColor:'white',height:'100%'}}> <ReactEcharts option={cmapOpt} style={{ width: '100%', height: '100%' }} notMerge /> </div> </Col> : '' } </Row> </React.Fragment> ); } } export default cMap;
TreeUtils
/** * Object對象相關的自定義處理函數(shù) */ const TreeUtils = { toTreeData(datas, { keyName = 'id', pKeyName = 'pid', titleName = 'name', normalizeTitleName = 'title', normalizeKeyName = 'key', parentName = 'fid' }, normalize = true, persistPrimaryData = false) { // 將普通數(shù)據(jù)轉(zhuǎn)成樹狀結構 // persistPrimaryData:保留原來的數(shù)據(jù) const tree = []; const noParentTemp = []; // 臨時存放所有父節(jié)點,一旦改父節(jié)點是另一個節(jié)點的子節(jié)點,那么就刪掉,最后剩下的就是沒有完整父節(jié)點的節(jié)點了 const isChildTemp = []; // 存放所有曾為子節(jié)點的節(jié)點 const relation = {}; // 存放節(jié)點數(shù)據(jù)及其之間的關系 // 遍歷數(shù)據(jù) datas.forEach((data) => { const key = data[keyName]; const pKey = data[pKeyName]; const title = data[titleName]; // 記錄所有的子節(jié)點信息 isChildTemp.push(key); // 記錄暫時還沒有發(fā)現(xiàn)父節(jié)點的項 if (!noParentTemp.includes(pKey)) { noParentTemp.push(pKey); } // 如果發(fā)現(xiàn)該項在"暫時沒有完整父節(jié)點"數(shù)組中,那么就從數(shù)組中刪除掉 if (noParentTemp.includes(key)) { noParentTemp.splice(noParentTemp.indexOf(key), 1); } // 將當前項的數(shù)據(jù)存在relation中 const itemTemp = normalize ? { [normalizeKeyName]: key, [normalizeTitleName]: title, [parentName]: pKey } : { ...data }; if (persistPrimaryData) { Object.assign(itemTemp, { primaryData: data }); } if (!relation[key]) { relation[key] = {}; } Object.assign(relation[key], itemTemp); // 將當前項的父節(jié)點數(shù)據(jù)也存在relation中 if (!relation[pKey]) { relation[pKey] = normalize ? { [normalizeKeyName]: pKey } : { [keyName]: pKey }; } // 如果作為父節(jié)點,沒有children.那么就加上 if (!relation[pKey].children) { relation[pKey].children = []; } // 將父子節(jié)點通過children關聯(lián)起來,形成父子關系 relation[pKey].children.push(relation[key]); }); // 將沒有完整父節(jié)點的節(jié)點過濾一下,剩下的就是沒有父節(jié)點的節(jié)點了(如果只有一個,那就是根節(jié)點根節(jié)點) noParentTemp.forEach((key) => { if (!isChildTemp.includes(key)) { tree.push(relation[key]); } }); return tree; }, toTreeMapData(datas, { keyName = 'id', pKeyName = 'pid', titleName = 'name', normalizeTitleName = 'title', normalizeKeyName = 'key', parentName = 'fid' }, normalize = true, persistPrimaryData = false) { // 將普通數(shù)據(jù)轉(zhuǎn)成樹狀結構 // persistPrimaryData:保留原來的數(shù)據(jù) const tree = []; const noParentTemp = []; // 臨時存放所有父節(jié)點,一旦改父節(jié)點是另一個節(jié)點的子節(jié)點,那么就刪掉,最后剩下的就是沒有完整父節(jié)點的節(jié)點了 const isChildTemp = []; // 存放所有曾為子節(jié)點的節(jié)點 const relation = {}; // 存放節(jié)點數(shù)據(jù)及其之間的關系 // 遍歷數(shù)據(jù) datas.forEach((data) => { const key = data[keyName]; const pKey = data[pKeyName]; const title = data[titleName]; const itemStyle = data['itemStyle'] const label = data['label'] // 記錄所有的子節(jié)點信息 isChildTemp.push(key); // 記錄暫時還沒有發(fā)現(xiàn)父節(jié)點的項 if (!noParentTemp.includes(pKey)) { noParentTemp.push(pKey); } // 如果發(fā)現(xiàn)該項在"暫時沒有完整父節(jié)點"數(shù)組中,那么就從數(shù)組中刪除掉 if (noParentTemp.includes(key)) { noParentTemp.splice(noParentTemp.indexOf(key), 1); } // 將當前項的數(shù)據(jù)存在relation中 const itemTemp = normalize ? { [normalizeKeyName]: key, [normalizeTitleName]: title, [parentName]: pKey ,'itemStyle':itemStyle,'label':label} : { ...data }; if (persistPrimaryData) { Object.assign(itemTemp, { primaryData: data }); } if (!relation[key]) { relation[key] = {}; } Object.assign(relation[key], itemTemp); // 將當前項的父節(jié)點數(shù)據(jù)也存在relation中 if (!relation[pKey]) { relation[pKey] = normalize ? { [normalizeKeyName]: pKey } : { [keyName]: pKey }; } // 如果作為父節(jié)點,沒有children.那么就加上 if (!relation[pKey].children) { relation[pKey].children = []; } // 將父子節(jié)點通過children關聯(lián)起來,形成父子關系 relation[pKey].children.push(relation[key]); }); // 將沒有完整父節(jié)點的節(jié)點過濾一下,剩下的就是沒有父節(jié)點的節(jié)點了(如果只有一個,那就是根節(jié)點根節(jié)點) noParentTemp.forEach((key) => { if (!isChildTemp.includes(key)) { tree.push(relation[key]); } }); return tree; }, }; export default TreeUtils;
總結:
對兩種樹狀圖進行功能結合,并且添加搜索結果聯(lián)動,感覺自己寫的功能代碼不夠簡潔,如果有問題歡迎大家積極提出討論,共同進步~
以上就是react echarts tree樹圖搜索展開功能示例詳解的詳細內(nèi)容,更多關于react echarts tree樹圖搜索的資料請關注腳本之家其它相關文章!
相關文章
React?Hooks的useState、useRef使用小結
React Hooks 是 React 16.8 版本引入的新特性,useState和useRef是兩個常用的Hooks,本文主要介紹了React?Hooks的useState、useRef使用,感興趣的可以了解一下2024-01-01React Native 集成 ArcGIS 地圖的詳細過程
ArcGIS官方提供了 JavaScript SDK,也提供了 ArcGIS-Runtime-SDK-iOS,但是并沒有提供 React Native的版本,所以這里使用了 react-native-arcgis-mapview 庫,本文給大家介紹React Native 集成 ArcGIS 地圖的詳細過程,感興趣的朋友跟隨小編一起看看吧2024-06-06詳解關于React-Router4.0跳轉(zhuǎn)不置頂解決方案
這篇文章主要介紹了詳解關于React-Router4.0跳轉(zhuǎn)不置頂解決案,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-05-05