乐趣区

有需求-那必须满足

前段时间自己用 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%;
  }
}
退出移动版