有需求? 那必須知足!

前段時間本身用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;
複製代碼

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%;
  }
}
複製代碼
相關文章
相關標籤/搜索