前段時間本身用echarts(graphic)寫的一個醫用三測單的組件,有朋友找我要源碼!css
有需求?html
有需求那必須知足啊!前端
上一篇文章圖片什麼都有我就不重複了,因爲時間屬實是太倉促,在這段代碼中,我刪除了接口數據及其餘功能,因此代碼拿過來就能用,至於折現和拐點的那段代碼,須要數據配合,這裏就直接刪除了,後期有時間,必定更新上!react
每一個代碼塊都有對應的註釋,閱讀應該不是問題,感謝閱讀,謝謝。segmentfault
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%;
}
}
複製代碼