// 数据处理 import * as d3 from 'd3'; import {symbol} from 'd3-shape'; import { degreesOnline, disconnectEvents, getBrokenLine, getHeartRate, getType, getTypeAnimalHeat, getTypeData, getTypeDatas, postpartumDays, setMergeTag, } from './utils'; import {BOTTOM_KEYS, HEAD_HEIGHT, LINE_HEIGHT, symbolArrowHeight, textLeftMargin, TOP_KEYS,} from './config'; export const iconDrawObj = { // 绘制圆形图标 getDrawRoundIcon: ({ content, data, x, y, fill = 'blue', stroke = 'blue', r = 5 }) => { // 增加icon 红色空心 content .append('g') .attr('fill', fill) .attr('stroke', stroke) .attr('stroke-width', 1) .selectAll('circle') .data(data) .join('circle') .attr('transform', (i) => { const yVal = y(i) || 0; if (!yVal) { return 'scale(0)'; } return ''; }) .attr('cx', x) .attr('cy', y) .attr('r', (i) => { if (y(i)) { return r; } return 0; }); }, // 绘制圆形点图标 getDrawRoundDotIcon: ({ content, data, x, y, fill = 'white', stroke = 'blue', deepFill = 'blue', r = 6, }) => { content .append('g') .attr('fill', fill) .attr('stroke', stroke) .attr('stroke-width', 1) .selectAll('circle') .data(data) .join('circle') .attr('transform', (i) => { const yVal = y(i) || 0; if (!yVal) { return 'scale(0)'; } return ''; }) .attr('cx', x) .attr('cy', y) .attr('r', r) .clone() .attr('cx', x) .attr('cy', y) .attr('r', 1) .attr('fill', deepFill); }, // 绘制x getDrawXIcon: ({ content, data, x, y, fill = 'blue', stroke = 'blue' }) => { content .append('g') .attr('fill', fill) .attr('stroke', stroke) .attr('stroke-width', 1) .selectAll('line') .data(data) .join('line') .attr('transform', (i) => { let yVal = y; if (typeof y === 'function') { yVal = y(i) || 0; } if (!yVal) { return 'scale(0)'; } return ''; }) .attr('x1', function (d) { return x(d) - 4; }) .attr('y1', function (d) { return y(d) - 4; }) .attr('x2', function (d) { return x(d) + 4; }) .attr('y2', function (d) { return y(d) + 4; }) .clone() .attr('x1', function (d) { return x(d) + 4; }) .attr('y1', function (d) { return y(d) - 4; }) .attr('x2', function (d) { return x(d) - 4; }) .attr('y2', function (d) { return y(d) + 4; }); }, // 绘制三角形 drawThreeIcon: ({ content, data, x, y, fill = 'blue', stroke = 'blue', riangle = 48 }) => { // 蓝色三角形 content .append('g') .attr('class', 'ceshiname') .selectAll('g') .data(data) .join('g') .attr('transform', (i) => { const yVal = y(i) || 0; if (!yVal) { return 'scale(0)'; } return `translate(${x(i)},${yVal})`; }) .append('path') .call((path) => { const symbolThree = symbol(); const symbolIndex = 5; symbolThree.type(d3.symbols[symbolIndex]); path.attr('d', symbolThree.size(riangle)).attr('fill', fill).attr('stroke', stroke); // .transition() // .duration(1500) // .attr('d', symbolThree.size(48)) }); }, }; export function getG(svg, viewConfig) { return svg .append('g') .attr('transform', `translate(${viewConfig.marginLeft},${viewConfig.marginTop + HEAD_HEIGHT})`); } // 设置数据 export function getData(allData) { const rowsData = allData.rows; // allData, '【全部数据】' const infoData = allData.grParamBOS; const typesData = getTypeDatas(allData.types, allData.grParamBOS.beginDate); const selectOp = allData.selectOp; const symbolTextArr = getTypeData('018', rowsData, false, allData.grParamBOS.beginDate); // 【特殊标记】 const symbolGoUp = getType('003', rowsData, allData.grParamBOS.beginDate); // 不升 // 体温单特殊数据 const dicData = JSON.parse(window.localStorage.getItem('transDictCode')); const signsManagementList = dicData ? dicData .filter((item) => item.name === '95')[0] .concept.sort((a, b) => { return a.displayOrder - b.displayOrder; }) : []; const otherArr = []; if (signsManagementList.length > 0) { for (let j = 0; j < signsManagementList.length; j++) { if ((signsManagementList[j].code !== '9502') & (signsManagementList[j].code !== '9503')) { const otherArrItem = getTypeData( signsManagementList[j].code, rowsData, false, allData.grParamBOS.beginDate ); otherArr.push(otherArrItem); } } } const surgeryNumDays = allData.types.filter((item) => item.typeCode === '031'); const surgeryArr = getTypeData('9502', rowsData, false, allData.grParamBOS.beginDate); // '【手术】' const minSurgeryArr = getTypeData('9503', rowsData, false, allData.grParamBOS.beginDate); // '【小手术】' const temArr = getTypeData('00301', rowsData, false, allData.grParamBOS.beginDate); // '【体温拒测等】' // const symbolGoUp = getType('003', rowsData, allData.grParamBOS.beginDate) // symbolGoUp, '不升' // 35度线上的内容 const symbolDegreesEvents = degreesOnline(symbolTextArr, selectOp, 0); const symbolDegreesOnline = disconnectEvents(symbolTextArr, selectOp, 'isBeforeAfter', 0); // 40~42线上的内容 const symbolTopOnline = degreesOnline(symbolTextArr, selectOp, 1); const symbolTopDegreesOnline = disconnectEvents(symbolTextArr, selectOp, 'isBeforeAfter', 1); const symbolContent = getTypeData('013', rowsData, false, allData.grParamBOS.beginDate); // symbolContent, '【标记内容】' const mergeTag = setMergeTag(symbolTopOnline, symbolContent); // mergeTag, '【合并标记,标记内容】' // 产后日数 infoData.postpartum = postpartumDays('031', typesData); // infoData, '产后日数' // 写死的先 infoData.dateClosed = { stopTime: true, // 控制结束日期 stopNumber: true, // 控制住院天数 }; // 折线 const brokenLineData = getBrokenLine( '003', rowsData, symbolDegreesOnline, symbolGoUp, symbolTopDegreesOnline, allData.grParamBOS.beginDate ); // 模拟数据 体温 const datasetAnus = getTypeAnimalHeat('003', rowsData, '1', allData.grParamBOS.beginDate); // datasetAnus, '腋温【x】' const bodyData = getTypeAnimalHeat('003', rowsData, '2', allData.grParamBOS.beginDate); // dbodyData, '口温' // const datasetHeartrate = getTypeAnimalHeat('003', rowsData) const datasetHeartrate = getTypeAnimalHeat('003', rowsData, '3', allData.grParamBOS.beginDate); // datasetHeartrate, '肛温【红空圆】' const earCool = getTypeAnimalHeat('003', rowsData, '4', allData.grParamBOS.beginDate); // earCool, '耳朵温' const painScore = getHeartRate( // painScore, '疼痛' '016', rowsData, symbolDegreesOnline, symbolTopDegreesOnline, true, allData.grParamBOS.beginDate ); const allTemperatureData = [bodyData, datasetAnus, datasetHeartrate, earCool]; // 所有的温度记录 // '脉搏【红实圆】' const datasetPulse = getHeartRate( '002', rowsData, symbolDegreesOnline, symbolTopDegreesOnline, true, allData.grParamBOS.beginDate ); // 心率【空心】 const datasetHeartRate = getHeartRate( '014', rowsData, symbolDegreesOnline, symbolTopDegreesOnline, true, allData.grParamBOS.beginDate ); // const allTemperatureData = getDrawData('003', rowsData, '1', allData.grParamBOS.beginDate) // datasetAnus, '腋温【x】' // const dataCool = getDrawCoolData('015', rowsData, true, allData.grParamBOS.beginDate) // dataCool, '【物理降温】' const dataCool = getTypeData('015', rowsData, true, allData.grParamBOS.beginDate); // 【物理降温】 // 呼吸【黑实圆】 const datasetPain = getTypeData('001', rowsData, false, allData.grParamBOS.beginDate); // 呼吸 const title = infoData.title; return { title, datasetHeartRate, bodyData, datasetAnus, datasetHeartrate, earCool, painScore, allTemperatureData, datasetPulse, symbolContent, symbolTextArr, mergeTag, dataCool, datasetPain, typesData, infoData, rowsData, symbolDegreesOnline, symbolGoUp, symbolDegreesEvents, brokenLineData, otherArr, temArr, surgeryArr, minSurgeryArr, surgeryNumDays, }; } export function drawTopMask(svg, viewConfig) { // ===================== // // 遮罩层挡住超出的折线 // // =====================// svg .append('g') .attr('transform', `translate(${viewConfig.marginLeft},${HEAD_HEIGHT - LINE_HEIGHT})`) .append('rect') .attr('class', 'mask-rect') .attr('x', 0) .attr('y', -100) .attr('width', viewConfig.contentWidth) // 2.5是上下的一个区域 1 是原来有 2的宽度 .attr('height', LINE_HEIGHT * (TOP_KEYS.length + 7.5) - 1) .attr('stroke', viewConfig.stroke) .attr('fill', '#fff') .attr('style', 'stroke-width: 0'); drawTopVerticalLine(svg, viewConfig); } export function drawTopVerticalLine(svg, viewConfig) { // 补上下竖线 let start = viewConfig.step; const lineG = getG(svg, viewConfig).append('g').attr('class', 'maskline-top'); while (start < viewConfig.contentWidth) { // const isLastLine = (start + viewConfig.step) >= viewConfig.contentWidth lineG .append('line') .attr('fill', 'stroke') .attr('x1', start) .attr('y1', 0) .attr('y2', (TOP_KEYS.length + 1) * LINE_HEIGHT) .attr('x2', start) .attr('stroke', start > viewConfig.step ? '#B22222' : viewConfig.stroke) // .attr('stroke', isLastLine ? 'black' : (start > viewConfig.step ? '#B22222' : viewConfig.stroke)) .attr('stroke-width', 4); // 设置线条宽度为2 start = start + viewConfig.step; } } export function drawBottomMask(svg, viewConfig) { const g = getG(svg, viewConfig); // 遮罩层挡住超出的折线 g.append('rect') .attr('class', 'mask-rect') .attr('x', 4) .attr('y', viewConfig.bottomKeysPosStart + viewConfig.micoStep * 2) .attr('width', viewConfig.contentWidth) .attr('height', LINE_HEIGHT * (BOTTOM_KEYS.length + 5)) .attr('stroke', viewConfig.stroke) .attr('fill', '#ffffff') .attr('style', 'stroke-width: 0'); drawBottomMaskLine(svg, viewConfig); } function drawBottomMaskLine(svg, viewConfig) { // 补上下竖线 let start = viewConfig.step; const lineG = getG(svg, viewConfig).append('g').attr('class', 'maskline'); while (start < viewConfig.contentWidth) { // const isLastLine = (start + viewConfig.step) >= viewConfig.contentWidth; lineG .append('line') .attr('fill', 'stroke') .attr('x1', start) .attr('y1', viewConfig.bottomKeysPosStart) .attr('y2', viewConfig.tableHeight) .attr('x2', start) .attr('stroke', start > viewConfig.step ? '#B22222' : viewConfig.stroke) //.attr('stroke', isLastLine ? 'black' : (start > viewConfig.step ? '#B22222' : viewConfig.stroke)) .attr('stroke-width', 4); // 设置线条宽度为2 start = start + viewConfig.step; } } // 绘制特殊事件文字 --- 有时间 export function drawSpecialText(svg, viewConfig, textData) { const g = getG(svg, viewConfig); g.append('g') .selectAll('text') .data(textData) .join('text') .attr('style', 'font-size:14px; fill: red;') .attr('class', 'mytext') .html((d, i) => { const t = (d?.value || '').split(','); let time = ''; if (t.length > 1) { time = t[0] + '于' + transNum(t[1].split(':')[0]) + '时' + transNum(t[1].split(':')[1]) + '分'; // time = t[0] + '㇑' + transNum(t[1].split(':')[0]) + '时' + transNum(t[1].split(':')[1]) + '分' } const texts = time.split(''); return texts .map((text, i) => { return `${text}`; }) .join(''); }) .attr('x', (d, i) => { return viewConfig.step + d.index * viewConfig.micoStep + textLeftMargin; }) .attr('y', () => { return viewConfig.topKeysPos - textLeftMargin; }); } // 绘制特殊事件文字 --- 大手术小手术 export function drawSurgery(svg, viewConfig, textData) { const g = getG(svg, viewConfig); const textDatas = []; if (textData.length > 0) { for (let j = 0; j < textData.length; j++) { let item = {}; if (textData[j].value) { item = { index: textData[j].index, value: textData[j].value.split(',')[0], date: textData[j].date, }; } else { item = textData[j]; } textDatas.push(item); } } g.append('g') .selectAll('text') .data(textDatas) .join('text') .attr('style', 'font-size:15px; fill: red;') .attr('class', 'mytext') .html((d, i) => { const texts = (d?.value || '').split(''); return texts .map((text, i) => { return `${text}`; }) .join(''); }) .attr('x', (d, i) => { return viewConfig.step + d.index * viewConfig.micoStep + textLeftMargin - 2; }) .attr('y', () => { return viewConfig.topKeysPos - textLeftMargin; }); } // 绘制特殊事件文字 --- 无时间 export function drawSpecialNoTimeText(svg, viewConfig, textData) { const g = getG(svg, viewConfig); const textDatas = []; if (textData.length > 0) { for (let j = 0; j < textData.length; j++) { let item = {}; if (textData[j].value) { item = { index: textData[j].index, value: textData[j].value.split(',')[0], date: textData[j].date, }; } else { item = textData[j]; } textDatas.push(item); } } g.append('g') .selectAll('text') .data(textDatas) .join('text') .attr('style', 'font-size:15px; fill: red;') .attr('class', 'mytext') .html((d, i) => { const texts = (d?.value || '').split(''); // let time = '' // if (t.length > 1) { // time = t[0] + '㇑' + transNum(t[1].split(':')[0]) + '时' + transNum(t[1].split(':')[1]) + '分' // } // const texts = time.split('') return texts .map((text, i) => { return `${text}`; }) .join(''); }) .attr('x', (d, i) => { return viewConfig.step + d.index * viewConfig.micoStep + textLeftMargin - 2; }) .attr('y', () => { return viewConfig.topKeysPos - textLeftMargin; }); } // 数字转换成文字 function transNum(num) { const arr1 = ['', '十']; const arr2 = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']; const sw = num.split('')[0]; const gw = num.split('')[1]; const str1 = sw > 1 ? arr2[sw] + '十' : arr1[sw]; let strNum = str1 + arr2[gw]; // 若H为十点整小时,则把零去掉,若m为十分,则去掉零 if (strNum.indexOf('十') > 0 || strNum.indexOf('零') === 1) { strNum = strNum.replace('零', ''); } return strNum; } // 绘制标签文字 export function drawTagText(svg, viewConfig, textData) { const g = getG(svg, viewConfig); g.append('g') .selectAll('text') .data(textData) .join('text') .attr('style', 'font-size:14px; fill: black;') .attr('class', 'mytext') .html((d, i) => { const texts = (d?.value || '').split(''); return texts .map((text, i) => { return `${text}`; }) .join(''); }) .attr('x', (d, i) => viewConfig.step + d.index * viewConfig.micoStep + textLeftMargin) .attr('y', () => { return viewConfig.topKeysPos - textLeftMargin + 198; }); } // 绘制特殊事件文字底部 export function drawBottomSpecialText(svg, viewConfig, textData) { const g = getG(svg, viewConfig); g.append('g') .selectAll('text') .data(textData) .join('text') .attr('style', 'font-size:14px; fill: blue;') .attr('class', 'mytext') .html((d, i) => { const texts = (d?.value || '').split(''); return texts .map((text, i) => { return `${text}`; }) .join(''); }) .attr('x', (d, i) => viewConfig.step + d.index * viewConfig.micoStep + textLeftMargin) .attr('y', (d) => { const texts = (d?.value || '').split(''); return viewConfig.bottomKeysPosStart - texts.length * LINE_HEIGHT - 6 - 58; }); } export function initArrow(svg) { const arrowMarkerRed = svg .append('defs') .append('marker') .attr('id', 'redArrow') .attr('markerUnits', 'strokeWidth') .attr('markerWidth', '12') .attr('markerHeight', '12') .attr('viewBox', '0 0 12 12') .attr('refX', '6') .attr('refY', '6') .attr('orient', 'auto'); const arrowPath = 'M2,2 L10,6 L2,10 L6,6 L2,2'; arrowMarkerRed.append('path').attr('d', arrowPath).attr('fill', 'red'); const arrowMarkerBlue = svg .append('defs') .append('marker') .attr('id', 'blueArrow') .attr('markerUnits', 'strokeWidth') .attr('markerWidth', '12') .attr('markerHeight', '12') .attr('viewBox', '0 0 12 12') .attr('refX', '6') .attr('refY', '6') .attr('orient', 'auto'); arrowMarkerBlue.append('path').attr('d', arrowPath).attr('fill', 'blue'); } export function lineArrow({ svg, viewConfig, scale, data } = {}) { const g = getG(svg, viewConfig); const vaildData = data.filter((item) => item); // { // ...item, // value: ismax ? maxDefault : minDefault, // sourceValue: item, // ismax: ismax , // max, // min // } // 绘制直线 g.selectAll('line') .data(vaildData) .join('line') .attr('x1', (d, i) => { return scale(d.index) + +viewConfig.X_OFFSET; }) .attr('y1', (d, i) => { if (d.ismax) { // 顶部箭头 return viewConfig.topKeysPos + symbolArrowHeight * 1.5; } else { // 底部箭头 return viewConfig.bottomKeysPosStart - symbolArrowHeight * 1.5; } }) .attr('x2', (d, i) => { return scale(d.index) + +viewConfig.X_OFFSET; }) .attr('y2', (d, i) => { if (d.ismax) { // 顶部箭头 return viewConfig.topKeysPos + symbolArrowHeight / 2; } else { // 底部箭头 return viewConfig.bottomKeysPosStart - symbolArrowHeight / 2; } }) .attr('stroke', (d) => { return d.ismax ? 'red' : 'blue'; }) .attr('stroke-width', 2) .attr('marker-end', (d, i) => { return d.ismax ? 'url(#redArrow)' : 'url(#blueArrow)'; }); g.selectAll('text') .data(vaildData) .join('text') .attr('class', 'desctextname') .html((item, i) => { const value = `${item.sourceValue}`.split(''); const dotIndex = value.indexOf('.'); return value .map((d, index) => { let y; let multiple = index; let addition = 0; // dot height if (item.ismax) { if (index >= dotIndex) { addition = 8; multiple = index - 1; } y = viewConfig.topKeysPos + symbolArrowHeight * 1.5 + (2 + multiple * 1.5) * viewConfig.X_OFFSET + addition; } else { if (index >= dotIndex) { addition = 9; multiple = index - 1; } y = viewConfig.bottomKeysPosStart - (2 + (value.length - multiple) * 1.5) * viewConfig.X_OFFSET + addition; } return ` ${d}`; }) .join(''); }) .attr('x', (item, i) => { return scale(item.index) + viewConfig.X_OFFSET - 4; }) .attr('y', (item) => { if (item.ismax) { return viewConfig.topKeysPos + symbolArrowHeight * 1.5 + LINE_HEIGHT / 2; } else { return viewConfig.bottomKeysPosStart - symbolArrowHeight / 2 - LINE_HEIGHT / 2; } }) .attr('style', (item) => { return `font-size:14px;fill:${item.ismax ? 'red' : 'blue'};font-weight: bold;`; }); }