前段时间自己用 echarts(graphic) 写的一个医用三测单的组件,有朋友找我要源码!
有需求?
有需求那必须满足啊!
上一篇文章图片什么都有我就不重复了,由于时间属实是太仓促,在这段代码中,我删除了接口数据及其他功能,所以代码拿过来就能用,至于折现和拐点的那段代码,需要数据配合,这里就直接删除了,后期有时间,一定更新上!
买个代码块都有对应的注释,阅读应该不是问题,感谢阅读,谢谢。
网格源码
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:
}
});
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;
css 样式
.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%;
}
}