前段時間本身用 echarts(graphic)寫的一個醫用三測單的組件,有朋友找我要源碼!
有需求?css
有需求那必須知足啊!html
上一篇文章圖片什麼都有我就不重複了,因爲時間屬實是太倉促,在這段代碼中,我刪除了接口數據及其餘功能,因此代碼拿過來就能用,至於折現和拐點的那段代碼,須要數據配合,這裏就直接刪除了,後期有時間,必定更新上!前端
買個代碼塊都有對應的註釋,閱讀應該不是問題,感謝閱讀,謝謝。react
import React, {PureComponent} from 'react'; import styles from './LineCharts.less'; import eCharts from 'echarts/lib/echarts'; import 'echarts/lib/component/tooltip'; import 'echarts/lib/component/title'; import 'echarts/lib/component/toolbox'; import 'echarts/lib/component/graphic'; /* * 網格線組件使用時須要計算寬度 * 建議整理好數據邏輯後直接傳入 * 按需求添加g組 * 鼠標hover事件暫時未添加,比較複雜時間不夠 * * */ // 前端配置不可刪除 const configFile = { 'PULSE': '脈搏(次/分)', 'HEART_RATE': '心率(次/分)', 'TEMPRATURE': '體溫', 'BREATH': '呼吸(次/分)', 'BLOOD_PRESSURE': '血壓(mmHg)', 'EMICTION': '總入量(ml)', 'INTAKE': '總出量(ml)', 'BOWELS_TIMES': '大便(次/日)', 'WEIGHT': '體重(kg)', 'HEIGHT': '身高(cm)', 'IS_REDUCTION_PAIN': '是否降痛', 'IS_PHYSICAL_COOLING': '是否物理降溫', 'PHYSICAL_COOLING_TEMPERATURE': '物理降溫溫度', 'MEASURE_POSITION': '測量位置', 'REDUCTION_PAIN_TARGET': '降痛目標', 'EMICTION_BOWEL': '出量-大便(ml)', 'EMICTION_VOMIT': '出量-嘔吐(ml)', 'EMICTION_URINE': '出量-小便(ml)', 'EMICTION_DRAIN': '出量-引流(ml)', 'EMICTION_PHLE': '出量-痰量(ml)', 'EMICTION_OTHER_OUTTAKE': '出量-其餘(ml)', 'TRANSFUSION_INTAKE': '靜脈入量(ml)', 'ORALLY_INTAKE': '口服入量(ml)', 'NASAL_FEED_INTAKE': '鼻飼入量(ml)', 'OTHER_INTAKE': '其餘入量(ml)', 'IS_REPEATED_MEASURE': '是否重複測量', 'PAIN_SCORE': '疼痛評分', }; // 中間展現維度 const left = 20, // 左邊距離 top = 180, // 上邊距離 rowSpacing = 18, // 每小格寬度 tRowNum = 3, // 頭部行數 mRowNum = 42;// 中部行數 // 左側座標軸第一行數據 const YAxisDataTop = [ { name: '脈 搏', position: [left + rowSpacing - 5, top + rowSpacing - 10], }, { name: '(次/分)', position: [left + rowSpacing - 5, top + rowSpacing + 6] }, { name: 180, position: [left + rowSpacing - 2, top + 2 * rowSpacing + 5], }, { name: '溫 度', position: [left + 4 * rowSpacing - 5, top + rowSpacing - 10], }, { name: '(℃)', position: [left + 4 * rowSpacing, top + rowSpacing + 6] }, { name: 42, position: [left + 4 * rowSpacing + 3, top + 2 * rowSpacing + 5] }, { name: '呼 吸', position: [left + 7 * rowSpacing - 2, top + rowSpacing - 10], }, { name: '(次/分)', position: [left + 7 * rowSpacing - 6, top + rowSpacing + 6] }, { name: 90, position: [left + 7 * rowSpacing + 4, top + 2 * rowSpacing + 5] }, ]; // 三測單左側軸數據 const YAxisData = [ [160, 41, 80], [140, 40, 70], [120, 39, 60], [100, 38, 50], [80, 37, 40], [60, 36, 30], [40, 35, 20] ]; class LineCharts extends PureComponent { constructor(props) { super(props); this.state = { whStyle: {}, divHtml: [], tableDatas: null, currentWeeks: 0, chartData: null, tooltipType: { isShow: 'none', left: 0, top: 0, text: [] }, spinning: 'spinning', }; } componentDidMount() { let self = this; let tableDatas = {left, top, rowSpacing}; let whStyle = { height: top + 50 + (43 + Object.keys(configFile).length) * rowSpacing, width: left * 2 + ((7 + 1) * 6 + 3) * rowSpacing < 962 ? 962 : left * 2 + ((7 + 1) * 6 + 3) * rowSpacing }; self.setState({ whStyle, currentWeeks: 1, tableDatas, spinning: 'showCharts', }, () => { self.renderECharts(tableDatas, whStyle); }) } // 切割數組 sliceArray = (array, size) => { let result = []; for (let i = 0; i < Math.ceil(array.length / size); i++) { let start = i * size; let end = start + size; result.push(array.slice(start, end)); } return result; }; // 獲取echarts 畫板 renderECharts = (tableDatas, whStyle) => { let self = this; let TSSParam = { columnNum: (7 + 1) * 6 + 3, // 計算有多少列 ...tableDatas }; let tableData = self.getTableData(TSSParam); let lineList = [...tableData]; let myChart = eCharts.init(document.getElementById('main')); if (whStyle) { myChart._zr.painter._height = whStyle.height; myChart._zr.painter._width = whStyle.width; } myChart.clear(); myChart.setOption({ title: { show: true, text: '體 溫 單', textStyle: { color: '#000', fontWeight: 'bold', fontSize: 32, align: 'left' }, left: 'center', top: '20px', }, toolbox: { feature: { saveAsImage: {} }, right: 30, top: 30, }, series: [], graphic: lineList }); }; // 時間計算 timeDifference = ($timeOne, $timeTwe) => { return parseInt((new Date($timeOne).getTime() - new Date($timeTwe).getTime()) / (60 * 60 * 24 * 1000)) + 1; }; // 拆分數據結構 splitJson = ($dataSet) => { let dataKeys = [], dataValue = []; for (let key in $dataSet) { dataKeys = [...dataKeys, key]; dataValue = [...dataValue, $dataSet[key]]; } return {keys: dataKeys, values: dataValue} }; // 處理折現數據 getLinePoints = ($type, $polylineData, $left, $top, $mRowNum, $rowSpacing) => { let pulseData = []; $polylineData.map((item, index) => { let yDistance = 0; if (item !== '') { if ($type === 'pulse' || $type === 'heartRate') { yDistance = $top + ($mRowNum - 1 - (item - 20) / 4) * $rowSpacing + 2; } else if ($type === 'temperature') { yDistance = $top + ($mRowNum - 1 - (item - 34) / 0.2) * $rowSpacing + 2; } else if ($type === 'breath') { yDistance = $top + ($mRowNum - 1 - (item - 10) / 2) * $rowSpacing + 2; } let coordinate = [ $left + (9.5 + index) * $rowSpacing + 3, Number.parseInt(yDistance) ]; pulseData = [...pulseData, coordinate] } }); return pulseData; }; // tooltip的移入移除 getTooltip = ($el, $totalDataTemplate, $index, $mouseType, $timeDataSet) => { let count = 0; if ($mouseType !== 'mouseOut') { for (let i = 0; i < $totalDataTemplate.length; i++) { if ($totalDataTemplate[i] !== '') { if ($index === count) { let elTypeText = ''; switch ($el.target.type) { case 'circle': elTypeText = '脈搏'; break; case 'ring': elTypeText = '心率'; break; case 'text': elTypeText = '體溫'; break; case 'image': elTypeText = '呼吸'; break; } let text = <div> <p><span>{elTypeText}: </span><span>{$totalDataTemplate[i]}</span></p> <p style={{marginBottom: 0}}>{$timeDataSet[i]}</p> </div>; this.setState({ tooltipType: { isShow: 'block', left: $el.event.clientX + 10, top: $el.event.clientY + 10, text: [text] } }); break } count++ } } } else { this.setState({ tooltipType: { isShow: 'none', left: 0, top: 0, text: [] } }); } }; // 繪製圖表 getTableData = ($TSSParam) => { let list = [], // 圖表數組 top = $TSSParam.top, // 頂部距離 left = $TSSParam.left, // 左側距離 rowSpacing = $TSSParam.rowSpacing, // 間距; inspectionCycle = [0, 4, 8, 12, 16, 20], // 巡查間隔數組 columnNum = $TSSParam.columnNum,// 列數 bRowNum = Object.keys(configFile).length + 1,// 尾部行數 tDataKeys = ['日 期', '住院天數', '手術後天數'], // 拆分topDataSet 保留key bDataKeys = this.splitJson(configFile).values;// 拆分bottomDataSet 保留key /*tableTitle數據繪製*/ let tableTitleList = this.getTableTitle(); list = [...list, ...tableTitleList]; /*獲取頭部部分:水平線 縱向線、以及文字數據錄入*/ let headerTextList = this.getHeaderTextList(tDataKeys, columnNum); list = [...list, ...headerTextList]; /*座標軸數據繪製*/ let yAxisList = this.getYAxisList(); list = [...list, ...yAxisList]; /*中間部分:水平線、縱向線、座標軸以及數據錄入*/ let middleList = this.getMiddleList(columnNum, inspectionCycle); list = [...list, ...middleList]; /*頁腳部分:水平線、縱向線、以及文字*/ let bottomList = this.getBottomList(bDataKeys, bRowNum, columnNum); list = [...list, ...bottomList]; return list; }; /*tableTitle數據繪製*/ getTableTitle = () => { let tableTitleList = []; // 中間數據 const tableTitle = { '姓名: ': '無名', '年齡: ': 36, '性別: ': '男', '科別: ': '-', '牀號: ': '-', '入院日期: ': '-', '住院病歷號: ': '-' }; let addLeft = left + 4; for (let keys in tableTitle) { let stroke = '', fill = ''; let template = { type: 'text', top: top - (tRowNum + 2) * rowSpacing + 5, left: addLeft, cursor: 'auto', style: { text: keys + tableTitle[keys], x: 0, y: 0, textAlign: 'left', textVerticalAlign: 'middle', fill: fill, font: 'italic none 12px cursive', stroke: stroke, lineWidth: 0 } }; addLeft += (keys.length + JSON.stringify(tableTitle[keys]).length) * 10; if ((keys.indexOf('入院日期') !== -1 || keys.indexOf('牀號') !== -1) && tableTitle[keys] !== '-') { addLeft -= tableTitle[keys].length * 4 } tableTitleList.push(template); } return tableTitleList; }; /*獲取頭部部分:水平線 縱向線、以及文字數據錄入*/ getHeaderTextList = (tDataKeys, columnNum) => { let listHTML = []; for (let i = 0; i <= tRowNum; i++) { let leftDistance = 0, lineLength = 0; if (i > 2 && i !== tRowNum) { leftDistance = left + 6 * rowSpacing; lineLength = (columnNum - 6) * rowSpacing } else { leftDistance = left; lineLength = columnNum * rowSpacing } listHTML.push({ type: 'line', top: top + (i - 4) * rowSpacing, left: leftDistance, cursor: 'auto', style: { stroke: !i ? 'rgb(0, 0, 0)' : 'rgba(0, 0, 0, .45)', lineWidth: 1, }, shape: { x1: 0, y1: 0, x2: lineLength, y2: 0 } }); } for (let j = 0; j <= columnNum; j++) { listHTML.push({ type: 'line', top: top - 4 * rowSpacing, left: left + j * rowSpacing, cursor: 'auto', style: { stroke: !j || j === columnNum ? 'rgb(0, 0, 0)' : 'rgba(0, 0, 0, .45)', lineWidth: 1, }, shape: { x1: 0, y1: 0, x2: 0, y2: !j || (j > 3 && !((j - 3) % 6)) ? tRowNum * rowSpacing : 0 } }); // 添加頭部數據文字 if (!(j % 6)) { for (let k = 0; k < tRowNum; k++) { let template = { type: 'text', top: null, left: null, cursor: 'auto', style: { text: null, x: 0, y: 0, textAlign: 'left', textVerticalAlign: 'middle', fill: '#000', font: 'italic none 12px cursive', stroke: null, lineWidth: 0 } }; if (!j) { template.top = top - (tRowNum + 1 - k) * rowSpacing + 4; template.left = left + 5; template.style.text = tDataKeys[k]; } else { if (j === columnNum) break; let count = j / 6; template.top = top - (tRowNum + 1 - k) * rowSpacing + 4; template.left = left + (count * 6 + 3) * rowSpacing + 5; } listHTML.push(template); } } } return listHTML }; /*座標軸數據繪製*/ getYAxisList = () => { let yAxisList = []; YAxisDataTop.map(item => { let stroke = '', fill = ''; if (item.name === '脈 搏') stroke = fill = 'red'; else if (item.name === '溫 度') stroke = fill = 'blue'; else stroke = fill = 'rgb(0, 0, 0)'; let template = { type: 'text', top: item.position[1], left: item.position[0], cursor: 'auto', style: { text: item.name, x: 0, y: 0, textAlign: 'left', textVerticalAlign: 'middle', fill: fill, font: 'italic none 12px cursive', stroke: stroke, lineWidth: 0 } }; yAxisList.push(template); }); return yAxisList }; /*中間部分:水平線、縱向線、座標軸以及數據錄入*/ getMiddleList = (columnNum, inspectionCycle) => { let middleList = []; for (let key = 0; key <= mRowNum; key++) { let leftDistance = 0, lineLength = 0; if (key > 2 && key !== mRowNum) { leftDistance = left + 9 * rowSpacing; lineLength = (columnNum - 9) * rowSpacing } else { leftDistance = left; lineLength = columnNum * rowSpacing } middleList.push({ type: 'line', top: top + (key - 1) * rowSpacing, left: leftDistance, cursor: 'auto', style: { stroke: key === mRowNum || !((key - 2) % 5) ? 'rgba(0, 0, 0, .45)' : 'rgba(0, 0, 0, .15)', lineWidth: 1, }, shape: { x1: 0, y1: 0, x2: lineLength, y2: 0 } }); // 添加左側y軸文字 let count = Math.floor((key - 1) / 5) - 1; if (key !== 2 && key !== mRowNum && (key - 1) % 5 === 1 && count < 7) { for (let keys in YAxisData[count]) { let lDistance = 0; if (keys === '0') { if (YAxisData[count][keys] >= 100) { lDistance = left + rowSpacing - 2; } else { lDistance = left + rowSpacing + 2; } } else if (keys === '1') { lDistance = left + 4 * rowSpacing + 3; } else { lDistance = left + 7 * rowSpacing + 4; } let template = { type: 'text', top: top + (key - 1) * rowSpacing + 4, left: lDistance, cursor: 'auto', style: { text: YAxisData[count][keys], x: 0, y: 0, textAlign: 'left', textVerticalAlign: 'middle', fill: '#000', font: 'italic none 12px cursive', stroke: null, lineWidth: 0 } }; middleList.push(template); } } } for (let index = 0; index <= columnNum; index++) { let lineLength = 0; if (index >= 9 || index === 0 || index === 3 || index === 6) { if (index === 3 || index === 6) lineLength = (mRowNum - 1) * rowSpacing; else lineLength = mRowNum * rowSpacing; } let $stroke; if (!((index - 3) % 6)) { if (index === columnNum) $stroke = 'rgb(0, 0, 0)'; else if (index === 3 || index === 6) $stroke = 'rgba(0, 0, 0, 0.45)'; else $stroke = 'red'; } else { if (!index || index === columnNum) $stroke = 'rgb(0, 0, 0)'; else if (index === 6) $stroke = 'rgba(0, 0, 0, 0.45)'; else $stroke = 'rgba(0, 0, 0, .15)' } middleList.push({ type: 'line', cursor: 'auto', top: index === 3 || index === 6 ? top : top - rowSpacing, left: left + index * rowSpacing, style: { stroke: $stroke, lineWidth: 1, }, shape: { x1: 0, y1: 0, x2: 0, y2: lineLength } }); /*添加巡查時間刻度*/ let template = { type: 'text', top: top - 10, left: inspectionCycle[index % 6] >= 10 ? left + (index + 3) * rowSpacing + 3 : left + (index + 3) * rowSpacing + 7, cursor: 'auto', style: { text: inspectionCycle[index % 6], x: 0, y: 0, textAlign: 'left', textVerticalAlign: 'middle', fill: '#000', font: 'italic none 12px cursive', stroke: null, lineWidth: 0 } }; if (index >= 6 && index < columnNum - 3) { middleList.push(template); } else if (index === 0) { template.left = left + 5; template.style.text = '時間'; middleList.push(template); } } return middleList; }; /*頁腳部分:水平線、縱向線、以及文字*/ getBottomList = (bDataKeys, bRowNum, columnNum) => { let bottomList = []; for (let keys = 0; keys <= bRowNum; keys++) { let leftDistance = 0; let lineLength = 0; if (keys > 0 && keys !== bRowNum) { leftDistance = left; lineLength = columnNum * rowSpacing; } bottomList.push({ type: 'line', cursor: 'auto', top: top + (mRowNum + keys - 1) * rowSpacing, left: leftDistance, style: { stroke: keys === bRowNum - 1 ? 'rgb(0, 0, 0)' : 'rgba(0, 0, 0, .45)', lineWidth: 1, }, shape: { x1: 0, y1: 0, x2: lineLength, y2: 0 } }); } for (let indexs = 0; indexs <= columnNum; indexs++) { let lineLength = 0; if (indexs >= 9) { if (!((indexs - 3) % 6)) { lineLength = (bRowNum - 1) * rowSpacing; } else if (!(indexs % 3)) { lineLength = rowSpacing; } } else if (indexs === 0) { lineLength = (bRowNum - 1) * rowSpacing; } bottomList.push({ type: 'line', cursor: 'auto', top: top + (mRowNum - 1) * rowSpacing, left: left + indexs * rowSpacing, style: { stroke: !indexs || indexs === columnNum ? 'rgb(0, 0, 0)' : 'rgba(0, 0, 0, .45)', lineWidth: 1, }, shape: { x1: 0, y1: 0, x2: 0, y2: lineLength } }); // 添加頁腳數據集 if (!(indexs % 6)) { let count = indexs / 6; for (let k = 0; k < bRowNum - 1; k++) { if (indexs === columnNum) break; if (!count) {// 第一列 let template = { type: 'text', top: null, left: null, cursor: 'auto', style: { text: null, x: 0, y: 0, textAlign: 'left', textVerticalAlign: 'middle', fill: '#000', font: 'italic none 12px cursive', stroke: null, lineWidth: 0 } }; template.top = top + (mRowNum - 1 + k) * rowSpacing + 4; template.left = left + 5; template.style.text = bDataKeys[k]; bottomList.push(template); } } } } return bottomList }; render() { const { state: { whStyle, spinning, }, } = this; return ( <div className={styles.echarts_box}> <div> <div className={styles.whStyleBox} style={{ width: '100%', height: '100%', opacity: spinning === 'showCharts' ? '1' : '0', }}> <div className={styles.main_box} style={{width: '100%', height: whStyle.height}}> <div id='main' className={styles.main} style={{ ...whStyle, marginLeft: `-${whStyle.width / 2}px` }} /> </div> </div> </div> </div> ) } } export default LineCharts;
.echarts_box { position: relative; overflow: auto; height: calc(~'100vh - 252px'); padding: 10px 24px; .whStyleBox { overflow: hidden; overflow-x: auto; .btn_box { float: right; margin-right: 10px; } .main_box { position: relative; clear: both; .main { position: absolute; left: 50%; } } } .tooltip { position: fixed; z-index: 9999; background: #fff; padding: 5px; border: 2px solid #000; border-radius: 5px; } .no_data_info { position: absolute; top: 0; left: 0; width: 100%; } }