import * as d3 from 'd3'; import { drawBottomMask, drawBottomSpecialText, drawSpecialNoTimeText, drawSpecialText, drawSurgery, drawTopMask, getData, getG, iconDrawObj, initArrow, lineArrow, } from './drawfn'; import {getMaxList} from './dataProcess'; import { bodyTemperature, BOTTOM_KEYS, endNumEnv, HEAD_HEIGHT, Header, heartRange, INFO_KEYS, leftTEXT, LINE_HEIGHT, nightTime, painScore, painTEXT, showPain, starNumEnv, TEXT_MARGIN_BOTTOM, textLeftMargin, timeNumber, TOP_KEYS, } from './config'; import ViewConfig from './ViewConfig'; export function init(data = []) { const ajaxData = getData(data); const chart = ConnectedScatterplot({ x: (d) => d.year, y: (d) => d.gas, xType: d3.scaleBand, yDomain: [starNumEnv, endNumEnv], width: 980, height: 1350 + BOTTOM_KEYS.length * 20 + 7, marginLeft: 10, marginRight: 10, marginTop: 10, marginBottom: 80, duration: 5000, // for the intro animation; 0 to disable renderData: ajaxData, }); if (document.getElementById('my_dataviz').children.length > 0) { document .getElementById('my_dataviz') .removeChild(document.getElementById('my_dataviz').children[0]); } document.getElementById('my_dataviz').appendChild(chart); } // Copyright 2021 Observable, Inc. // Released under the ISC license. // https://observablehq.com/@d3/connected-scatterplot // width = 640, // outer width, in pixels // height = 400, // outer height, in pixels // marginTop = 20, // top margin, in pixels // marginRight = 20, // right margin, in pixels // marginBottom = 50, // bottom margin, in pixels // marginLeft = 30, // left margin, in pixels // stroke = 'currentColor', // stroke color of line and dots // strokeWidth = 2, // stroke width of line and dots // strokeLinecap = 'round', // stroke line cap of line // strokeLinejoin = 'round', // stroke line join of line // renderData, function ConnectedScatterplot(options) { // 计算出各种位置常量 const viewConfig = new ViewConfig(options); let tooltip; const svg = d3 .create('svg') .attr('id', 'printsvg') .attr('width', viewConfig.width) .attr('height', viewConfig.height) .attr('viewBox', [0, 0, viewConfig.width, viewConfig.height]) .attr('style', 'max-width: 100%; height: auto; height: intrinsic;') .attr('transform', `translate(0,${viewConfig.marginTop})`); setTimeout(() => { tooltip = svg.append('g').style('pointer-events', 'none'); }); const xScale = d3.scaleBand().domain(d3.range(42)).rangeRound(viewConfig.xRange); // 体温的 scale const bodyScale = d3.scaleLinear([bodyTemperature[0], bodyTemperature[1]], viewConfig.yRange); // 心率的 Scale const heartScale = d3.scaleLinear([heartRange[0], heartRange[1]], viewConfig.yRange); // 疼痛的 scale const painScale = d3.scaleLinear([painScore[0], painScore[1]], viewConfig.yRange); // 心率上限 const bodyOverflowData = getMaxList({ list: viewConfig.renderData.datasetHeartRate.flat(Infinity), max: heartRange[1], min: heartRange[0], maxDefault: 180, minDefault: 40, }); // 脉搏上限 const datasetPulse = getMaxList({ list: viewConfig.renderData.datasetPulse.flat(Infinity), max: heartRange[1], min: heartRange[0], maxDefault: 180, minDefault: 40, }); // 口温下限 const bodyData = getMaxList({ list: viewConfig.renderData.bodyData, max: bodyTemperature[1], min: bodyTemperature[0], maxDefault: 42, minDefault: 35, }); // 腋温下限 const datasetAnus = getMaxList({ list: viewConfig.renderData.datasetAnus, max: bodyTemperature[1], min: bodyTemperature[0], maxDefault: 42, minDefault: 35, }); // 肛温下限 const datasetHeartrate = getMaxList({ list: viewConfig.renderData.datasetHeartrate, max: bodyTemperature[1], min: bodyTemperature[0], maxDefault: 42, minDefault: 35, }); // 耳温下限 const earCool = getMaxList({ list: viewConfig.renderData.earCool, max: bodyTemperature[1], min: bodyTemperature[0], maxDefault: 42, minDefault: 35, }); const allTemperature = [ bodyOverflowData, datasetPulse, bodyData, datasetAnus, datasetHeartrate, earCool, ]; // viewConfig.renderData.bodyData = levelingData({ // list: viewConfig.renderData.bodyData, // maxDefault: 43, // minDefault: 33 // }) initArrow(svg); // ========================== // 开始调用绘制函数 // ========================== // 1、绘制竖线格子 // 2、绘制折线图 // 3、绘制上下的字段属性(遮挡住超出的线条)【需要控制一个上下限,不超过遮罩层,体温【43°,】】 // 4、绘制被遮挡的竖线格子 // 一定要按照循序调用 // 绘制脉搏体温 drwaPulse(svg); // 绘制折线区域格子 drawbgLine(svg); // 绘制红色竖线 drwaVerticallLine(svg); // 绘制折线 viewConfig.renderData.brokenLineData.forEach((item) => { brokenLine(svg, item); }); const tmpList = getIndexValue( viewConfig.renderData.bodyData, viewConfig.renderData.datasetAnus, viewConfig.renderData.datasetHeartrate, viewConfig.renderData.earCool ); // 绘制脉搏线条 viewConfig.renderData.datasetPulse.forEach((item) => { const item2 = []; item.forEach((x) => { // const obj = tmpList.find((o) => o.index === x.index && o.value === heartScale(x.value)) const obj = tmpList.find( (o) => o.index === x.index && Math.abs(o.value - heartScale(x.value)) < 1 ); if (!obj) { item2.push(x); } }); drawHeart(svg, item, true); drawHeart(svg, item2, false); }); // 绘制心率线条 viewConfig.renderData.datasetHeartRate.forEach((item) => { drawHeartRate(svg, item); }); // 绘制口温线条 // drawPathBody(svg, viewConfig.renderData.bodyData) // 绘制腋温线条 drawAnus(svg, viewConfig.renderData.datasetAnus); // 绘制肛温线条 // drawJuhua(svg, viewConfig.renderData.datasetHeartrate) // 绘制耳温 // thermometer(svg, viewConfig.renderData.earCool) // 绘制疼痛 if (showPain) { viewConfig.renderData.painScore.forEach((item) => { drawPain(svg, item); }); } // 需要放在绘制折线后,起到遮挡折线的作用 // 绘制呼吸格子 drwaBreathing( svg, viewConfig.renderData.datasetPain.filter((x) => x.value !== null) ); // 需要放在绘制呼吸后,绘制下划线 // 绘制底部横线和值 // ===================== // // 遮罩层挡住超出的折线 // // =====================// drawTopMask(svg, viewConfig); drawBottomMask(svg, viewConfig); // 绘制底部线条 drwaBottomLine(svg); drwaBottomLineData(svg); // 绘制顶部值 drwaTopData(svg); // 绘制特殊事件文字 入院 离院项目 // drawSpecialText(svg, viewConfig, viewConfig.renderData.symbolTextArr) // drawSpecialText(svg, viewConfig, viewConfig.renderData.symbolContent) // 绘制特殊事件文字---有时间 if (viewConfig.renderData.otherArr.length > 0) { for (let j = 0; j < viewConfig.renderData.otherArr.length; j++) { drawSpecialText(svg, viewConfig, viewConfig.renderData.otherArr[j]); } } drawSurgery(svg, viewConfig, viewConfig.renderData.minSurgeryArr); drawSurgery(svg, viewConfig, viewConfig.renderData.surgeryArr); drawSpecialNoTimeText(svg, viewConfig, viewConfig.renderData.temArr); // 绘制标签文字 // drawTagText(svg, viewConfig, viewConfig.renderData.symbolContent) // drawBottomSpecialText(svg, viewConfig, viewConfig.renderData.symbolDegreesOnline); drawBottomSpecialText(svg, viewConfig, viewConfig.renderData.symbolGoUp); drawBottomSpecialText(svg, viewConfig, viewConfig.renderData.symbolDegreesEvents); // 绘制降温的红圈和虚线 drawCoolBody(svg, viewConfig.renderData.dataCool, viewConfig.renderData.allTemperatureData); // 绘制同一时间心率和脉搏的虚线 drawRateBody(svg, viewConfig.renderData.datasetPulse, viewConfig.renderData.datasetHeartRate); drwaOtherInfo(svg, viewConfig.renderData.infoData); // 最后一步 // 绘制四周加粗边框 drwaTabelBorder(svg); // ========== // // 最后展示浮层的内容 // ========== // allTemperature.forEach((item) => { lineArrow({ svg, viewConfig, scale: xScale, data: item, }); }); // ========================== // 结束调用绘制函数 // ========================== return svg.node(); // 绘制最外面边框 function drwaTabelBorder(svg) { const g = svg .append('g') .attr('transform', `translate(${viewConfig.marginLeft},${viewConfig.topPos})`); g.append('rect') .attr('x', 0) .attr('y', 0) .attr('width', viewConfig.contentWidth) .attr('height', viewConfig.tableHeight) .attr('stroke', viewConfig.stroke) .attr('fill', 'none') .attr('style', 'stroke-width: 4'); } // 绘制所有竖线 function drwaVerticallLine(svg) { const g = getG(svg, viewConfig); let start = viewConfig.step; g.attr('class', 'maskline2'); while (start < viewConfig.contentWidth) { g.append('line') .attr('x1', start) .attr('y1', 0) .attr('y2', viewConfig.tableHeight) .attr('x2', start) .attr('stroke', start > viewConfig.step ? '#B22222' : viewConfig.stroke) .attr('stroke-width', 4); // 设置线条宽度为2 start = start + viewConfig.step; } } // 绘制底部的线条 function drwaBottomLine(svg) { // 补上下竖线 let start = viewConfig.step; const lineG = getG(svg, viewConfig).append('g').attr('class', 'maskline'); const topPos = viewConfig.bottomKeysPosStart + 40; while (start < viewConfig.contentWidth) { BOTTOM_KEYS.forEach((item, i) => { const step = viewConfig.step / item.times; let itemStart = start; for (let j = 0; j < 6; j++) { if (((j * viewConfig.step) / 6) % step === 0) { lineG .append('line') .attr('fill', 'stroke') .attr('x1', itemStart) .attr('y1', topPos + i * LINE_HEIGHT) .attr('y2', topPos + (i + 1) * LINE_HEIGHT) .attr('x2', itemStart) .attr('stroke', viewConfig.stroke); } itemStart += viewConfig.step / 6; } }); start += viewConfig.step; } } // 绘制底部的线条和文字 function drwaBottomLineData(svg) { const g = getG(svg, viewConfig); // 绘制底部的横向线条 g.selectAll('line') .data([...BOTTOM_KEYS]) .join('line') .attr('x1', 0) .attr('y1', (d, i) => { const isLast = i === BOTTOM_KEYS.length - 1; return viewConfig.bottomKeysPosStart + (i + 2) * LINE_HEIGHT + (isLast ? 15 : 0); // return viewConfig.bottomKeysPosStart + (i + 2) * LINE_HEIGHT // 2是呼吸,占了2行 }) .attr('y2', (d, i) => { const isLast = i === BOTTOM_KEYS.length - 1; return viewConfig.bottomKeysPosStart + (i + 2) * LINE_HEIGHT + (isLast ? 15 : 0); // return viewConfig.bottomKeysPosStart + (i + 2) * LINE_HEIGHT }) .attr('x2', viewConfig.width - viewConfig.marginLeft - viewConfig.marginRight) .attr('fill', 'none') .attr('class', 'dataLine') .attr('stroke', viewConfig.stroke) .attr('stroke-width', 1) .attr('stroke-linejoin', viewConfig.strokeLinejoin) .attr('stroke-linecap', viewConfig.strokeLinecap); const textArr = BOTTOM_KEYS; const repeatArr = d3.range(8); // 绘制文字 textArr.map(({ key, name, times }, index) => { if (key !== '100') { if (times && times === 2) { let str = []; g.append('g') .selectAll('text') .data(repeatArr) .join('text') .attr('style', 'font-size:15px; font-weight:bold;fill: black') .attr('class', 'mytext') .text((i) => { if (i == 0) { return name; } else { const text = getTypeValue(key, viewConfig.renderData.typesData)[i - 1]?.typeValue; if (text) { str = text.split(','); if (str.length > 1) { return str[0]; } } return text; } }) .attr('x', (i) => { const pos = getTypeValue(key, viewConfig.renderData.typesData)[i - 1]?.index + 1; if (i === 0) { return textLeftMargin * 2; } return pos * viewConfig.step + textLeftMargin; }) .attr('y', () => { return viewConfig.bottomKeysPosStart + (index + 3) * LINE_HEIGHT - TEXT_MARGIN_BOTTOM; }); g.append('g') .selectAll('text') .data(repeatArr) .join('text') .attr('style', 'font-size:15px; font-weight:bold;fill: black') .attr('class', 'mytext') .text((i) => { if (i == 0) { return ''; } else { const text = getTypeValue(key, viewConfig.renderData.typesData)[i - 1]?.typeValue; if (text) { str = text.split(','); if (str.length > 1) { return str[1]; } } return ''; } }) .attr('x', (i) => { const pos = getTypeValue(key, viewConfig.renderData.typesData)[i - 1]?.index + 1; if (i === 0) { // return textLeftMargin * 2 } return pos * viewConfig.step + textLeftMargin + 60; }) .attr('y', () => { return viewConfig.bottomKeysPosStart + (index + 3) * LINE_HEIGHT - TEXT_MARGIN_BOTTOM; }); } else if (key === '017') { for (let j = 0; j < 8; j++) { const testText = getTypeValue(key, viewConfig.renderData.typesData)[j - 1]?.typeValue; if (j === 0) { g.append('g') .selectAll('text') .data(repeatArr) .join('text') .attr('style', 'font-size:15px;') .attr('class', 'mytext') .text(name) .attr('x', () => { return textLeftMargin * 2 + 10; }) .attr('y', () => { return ( viewConfig.bottomKeysPosStart + (index + 3) * LINE_HEIGHT - TEXT_MARGIN_BOTTOM + 1 ); }); } else if (testText !== undefined) { if (testText.length > 8) { for (let i = 0; i < testText.length; i += 8) { // 使用slice方法获取当前段的字符串 const line = testText.slice(i, i + 8); g.append('g') .selectAll('text') .data(repeatArr) .join('text') .attr('style', 'font-size:14px') .attr('class', 'mytext') .text(line) .attr('x', () => { const pos = getTypeValue(key, viewConfig.renderData.typesData)[j - 1]?.index + 1; return pos * viewConfig.step + textLeftMargin; }) .attr('y', () => { return ( viewConfig.bottomKeysPosStart + (index + 3) * LINE_HEIGHT - TEXT_MARGIN_BOTTOM + i * 2.5 ); }); } } else { g.append('g') .selectAll('text') .data(repeatArr) .join('text') .attr('style', 'font-size:14px;') .attr('class', 'mytext') .text(testText) .attr('x', () => { const pos = getTypeValue(key, viewConfig.renderData.typesData)[j - 1]?.index + 1; return pos * viewConfig.step + textLeftMargin; }) .attr('y', () => { return ( viewConfig.bottomKeysPosStart + (index + 3) * LINE_HEIGHT - TEXT_MARGIN_BOTTOM ); }); } } } } else { g.append('g') .selectAll('text') .data(repeatArr) .join('text') // .attr('style', 'font-size:15px; font-weight:bold') .attr('style', (i, d) => { // 使用回调函数来设置样式 let textContent = i === 0 ? name : getTypeValue(key, viewConfig.renderData.typesData)[i - 1]?.typeValue; let fontSize = '15px'; // 默认字体大小margin-left: 15px // 检查条件并设置不同的字体大小 if (key === '005' && textContent === '*') { fontSize = '30px'; // 或者你想要的任何特定大小 } // 构建并返回样式字符串 return `font-size: ${fontSize};font-weight:bold;`; // 注意这里只设置了字体大小,其他样式未受影响 }) .attr('class', 'mytext') .text((i) => { if (i === 0) { return name; } else { let textValue = getTypeValue(key, viewConfig.renderData.typesData)[i - 1] ?.typeValue; return textValue; } }) .attr('x', (i) => { let nameLocation = textLeftMargin * 2 + 10; if (i === 0) { if (key === '006') { nameLocation = textLeftMargin * 2 - 3; } return nameLocation; } const pos = getTypeValue(key, viewConfig.renderData.typesData)[i - 1]?.index + 1; const typesData = getTypeValue(key, viewConfig.renderData.typesData)[i - 1]; if (typesData) { const firstNameLength = typesData.typeValue.length; if (firstNameLength === 1) { return pos * viewConfig.step + textLeftMargin + 50; } else if (firstNameLength === 2) { return pos * viewConfig.step + textLeftMargin + 46; } else if (firstNameLength === 3) { return pos * viewConfig.step + textLeftMargin + 43; } else if (firstNameLength === 4) { if (typesData.typeCode === '010') { return pos * viewConfig.step + textLeftMargin + 45; } else { return pos * viewConfig.step + textLeftMargin + 40; } } else if (firstNameLength === 5) { return pos * viewConfig.step + textLeftMargin + 38; } else if (firstNameLength === 6) { return pos * viewConfig.step + textLeftMargin + 32; } else if (firstNameLength > 10) { return pos * viewConfig.step + textLeftMargin; } else { return pos * viewConfig.step + textLeftMargin + 32; } } else { return pos * viewConfig.step + textLeftMargin + 30; } }) .attr('y', (i) => { const daBianValue = getTypeValue(key, viewConfig.renderData.typesData)[i - 1] ?.typeValue; let textHeight = viewConfig.bottomKeysPosStart + (index + 3) * LINE_HEIGHT - TEXT_MARGIN_BOTTOM; if (key === '005' && daBianValue === '*') { textHeight = textHeight + 12; } return textHeight; }); // .attr('y', () => { // return ( // viewConfig.bottomKeysPosStart + (index + 3) * LINE_HEIGHT - TEXT_MARGIN_BOTTOM + 1 // ) // }) } } }); } // 绘制底部的线条和文字 // function drwaBottomLineData(svg) { // const g = getG(svg, viewConfig) // // 绘制底部的横向线条 // g.selectAll('line') // .data([...BOTTOM_KEYS]) // .join('line') // .attr('x1', 0) // .attr('y1', (d, i) => { // return viewConfig.bottomKeysPosStart + (i + 2) * LINE_HEIGHT // 2是呼吸,占了2行 // }) // .attr('y2', (d, i) => { // return viewConfig.bottomKeysPosStart + (i + 2) * LINE_HEIGHT // }) // .attr('x2', viewConfig.width - viewConfig.marginLeft - viewConfig.marginRight) // .attr('fill', 'none') // .attr('class', 'dataLine') // .attr('stroke', viewConfig.stroke) // .attr('stroke-width', 1) // .attr('stroke-linejoin', viewConfig.strokeLinejoin) // .attr('stroke-linecap', viewConfig.strokeLinecap) // const textArr = BOTTOM_KEYS // const repeatArr = d3.range(8) // // 绘制文字 // textArr.map(({ key, name, times }, index) => { // // 绘制血压 // if (times && times === 2) { // let str = [] // g.append('g') // .selectAll('text') // .data(repeatArr) // .join('text') // .attr('style', 'font-size:14px') // .attr('class', 'mytext') // .text((i) => { // if (i === 0) { // return name // } else { // const text = getTypeValue(key, viewConfig.renderData.typesData)[i - 1]?.typeValue // if (text) { // str = text.split(',') // if (str.length > 1) { // return str[0] // } // } // return text // } // }) // .attr('x', (i) => { // const pos = getTypeValue(key, viewConfig.renderData.typesData)[i - 1]?.index + 1 // if (i === 0) { // return textLeftMargin * 2 // } // return pos * viewConfig.step + textLeftMargin // }) // .attr('y', () => { // return viewConfig.bottomKeysPosStart + (index + 3) * LINE_HEIGHT - TEXT_MARGIN_BOTTOM // }) // g.append('g') // .selectAll('text') // .data(repeatArr) // .join('text') // .attr('style', 'font-size:14px; margin-left: 5px') // .attr('class', 'mytext') // .text((i) => { // if (i === 0) { // return name // } else { // const text = getTypeValue(key, viewConfig.renderData.typesData)[i - 1]?.typeValue // if (text) { // str = text.split(',') // if (str.length > 1) { // return str[1] // } // } // return '' // } // }) // .attr('x', (i) => { // const pos = getTypeValue(key, viewConfig.renderData.typesData)[i - 1]?.index + 1 // if (i === 0) { // return textLeftMargin * 2 // } // return pos * viewConfig.step + textLeftMargin + 60 // }) // .attr('y', () => { // return viewConfig.bottomKeysPosStart + (index + 3) * LINE_HEIGHT - TEXT_MARGIN_BOTTOM // }) // } else { // g.append('g') // .selectAll('text') // .data(repeatArr) // .join('text') // // .attr('style', 'font-size:14px') // .attr('style', (i, d) => { // 使用回调函数来设置样式 // let textContent = (i === 0) ? name : getTypeValue(key, viewConfig.renderData.typesData)[i - 1]?.typeValue; // let fontSize = '14px'; // 默认字体大小margin-left: 5px // // 检查条件并设置不同的字体大小 // if (key === '005' && textContent === '*') { // fontSize = '30px'; // 或者你想要的任何特定大小 // } // // 构建并返回样式字符串 // return `font-size: ${fontSize};`; // 注意这里只设置了字体大小,其他样式未受影响 // }) // .attr('class', 'mytext') // .text((i) => { // if (i === 0) { // return name // } else { // return getTypeValue(key, viewConfig.renderData.typesData)[i - 1]?.typeValue // } // }) // .attr('x', (i) => { // const pos = getTypeValue(key, viewConfig.renderData.typesData)[i - 1]?.index + 1 // if (i === 0) { // return textLeftMargin * 2 // } // return pos * viewConfig.step + textLeftMargin // }) // .attr('y', (i) => { // const daBianValue = getTypeValue(key, viewConfig.renderData.typesData)[i - 1]?.typeValue // let textHeight = viewConfig.bottomKeysPosStart + (index + 3) * LINE_HEIGHT - TEXT_MARGIN_BOTTOM // if(key === '005' && daBianValue === '*'){ // textHeight = textHeight + 12 // } // return textHeight // }) // } // }) // } // 绘制呼吸 function drwaBreathing(svg, breathData) { const g = getG(svg, viewConfig); // 遮罩层挡住超出的折线 g.append('rect') .attr('class', 'mask-rect') .attr('x', 0) .attr('y', viewConfig.bottomKeysPosStart) .attr('width', viewConfig.width - viewConfig.marginRight - viewConfig.marginLeft) .attr('height', viewConfig.micoStep * 2 - 1) .attr('stroke', viewConfig.stroke) .attr('fill', '#fff') .attr('style', 'stroke-width: 0'); // 绘制横线 g.append('line') .attr('x1', 0) .attr('y1', viewConfig.bottomKeysPosStart) .attr('y2', viewConfig.bottomKeysPosStart) .attr('x2', viewConfig.width - viewConfig.marginLeft - viewConfig.marginRight) .attr('fill', 'none') .attr('class', 'dataLine') .attr('stroke', viewConfig.stroke) .attr('stroke-width', 1) .attr('stroke-linejoin', viewConfig.strokeLinejoin) .attr('stroke-linecap', viewConfig.strokeLinecap); const textYPos = viewConfig.bottomKeysPosStart + LINE_HEIGHT + TEXT_MARGIN_BOTTOM; // 绘制汉字 g.append('text') .attr('style', 'font-size:15px;font-weight:bold') .text('呼 吸(次/分)') .attr('x', textLeftMargin * 2) .attr('y', textYPos); // 绘制数据 const data = d3.range(42); g.append('g') .selectAll('text') .data(data) .join('text') .attr('style', 'font-size:14px') .attr('class', 'mytext') .text((d) => { return breathData[d]?.value; }) .attr('x', (i) => { const index = breathData[i]?.index; return viewConfig.step + index * viewConfig.micoStep + 3; }) .attr('y', (i) => { if (i % 2) { return textYPos + 10; } else { return textYPos - 10; } }); // 绘制呼吸竖线 g.append('g') .selectAll('line') .data(data) .join('line') .attr('x1', (d, i) => { return viewConfig.step + i * viewConfig.micoStep; }) .attr('y1', viewConfig.bottomKeysPosStart + 2 * LINE_HEIGHT) .attr('x2', (d, i) => { return viewConfig.step + i * viewConfig.micoStep; }) .attr('y2', viewConfig.bottomKeysPosStart) .attr('fill', 'none') .attr('class', 'dataLine') .attr('stroke', viewConfig.stroke) .attr('stroke-width', 1) .attr('stroke-linejoin', viewConfig.strokeLinejoin) .attr('stroke-linecap', viewConfig.strokeLinecap); } // 绘制脉搏体温文字 function drwaPulse(svg) { // 左侧的文字 const g = getG(svg, viewConfig); g.append('line') .attr('class', 'slefline') .attr('y1', viewConfig.topKeysPos) // 这个还要修改一下 .attr('x1', viewConfig.step / 2) .attr('y2', viewConfig.bottomKeysPosStart) .attr('x2', viewConfig.step / 2) .attr('stroke', viewConfig.stroke); leftTEXT.map((texts, index) => { g.append('g') .selectAll('text') .data(texts) .join('text') .attr('style', `font-size:14px;fill:${['red', 'blue'][index]}`) .attr('class', 'mytext') .html((d, i) => { if (i === 0) { const value = d.split(','); return `${value[0]}${ value[1] }${value[2]}`; } return `${d}`; }) .attr('x', (d, i) => { // 调整坐标文字显示位置 if (i === 0) { if (index === 0) { return viewConfig.micoStep * Math.max(index * 4, 1) - 12; } return viewConfig.micoStep * Math.max(index * 4, 1) - 6; } return viewConfig.micoStep * Math.max(index * 4, 0.9); }) .attr('y', (d, i) => { if (i === 0) { return viewConfig.topKeysPos + viewConfig.micoStep * 1; } return viewConfig.topKeysPos + viewConfig.micoStep * 5 * i; }); }); // 绘制疼痛表格 if (showPain) { // 绘制疼痛分割线 g.append('line') .attr('x1', 0) .attr('y1', viewConfig.bottomKeysPosStart - viewConfig.micoStep * 6) .attr('y2', viewConfig.bottomKeysPosStart - viewConfig.micoStep * 6) .attr('x2', viewConfig.width - viewConfig.marginLeft - viewConfig.marginRight) .attr('fill', 'none') .attr('class', 'dataLine') .attr('stroke', viewConfig.stroke) .attr('stroke-width', 1) .attr('stroke-linejoin', viewConfig.strokeLinejoin) .attr('stroke-linecap', viewConfig.strokeLinecap); painTEXT.map((texts, index) => { g.append('g') .selectAll('text') .data(texts) .join('text') .attr('style', `font-size:14px;fill:${['black', 'blue'][index]}`) .attr('class', 'mytext') .html((d, i) => { if (d.length > 2) { const value = d.split(' '); return `${value[0]}${value[1]}${ value[2] }${value[3]}`; } else if (i === 0) { return `${d}`; } return `${d}`; }) .attr('x', (d, i) => { // 调整坐标文字显示位置 if (i === 0) { if (index === 0) { return viewConfig.micoStep * Math.max(index * 4, 1) - 12; } return viewConfig.micoStep * Math.max(index * 4, 1) - 6; } return viewConfig.micoStep * Math.max(index * 4, 0.9); }) .attr('y', (d, i) => { if (i === 0) { return viewConfig.bottomKeysPosStart - viewConfig.micoStep * 3.8 - 30; } return ( viewConfig.bottomKeysPosStart - viewConfig.micoStep * 3.8 + viewConfig.micoStep * i - 30 ); }); }); } } // 绘制折线的背景格子 function drawbgLine(svg) { const g = getG(svg, viewConfig); // 绘制横线 const horizontallength = (bodyTemperature[1] - bodyTemperature[0] + 1) * 5; const horizontalData = [...new Array(horizontallength).keys()]; g.append('g') .attr('class', 'line-content') .selectAll('line') .data(horizontalData) .join('line') // .attr('x1', viewConfig.step) .attr('x1', (d, i) => { // 判断是否是最后五条线,如果是,则减少几厘米 if (i >= horizontalData.length - 9) { return viewConfig.step - 60; // 向前缩短20像素 } // 判断是否是最后五条线,如果是,则减少几厘米 if (i >= horizontalData.length - 10) { return viewConfig.step - 120; // 向前缩短20像素 } return viewConfig.step; // 其他线使用默认长度 }) .attr('y1', (d, i) => { return viewConfig.topKeysPos + i * viewConfig.micoStep; }) .attr('y2', (d, i) => { return viewConfig.topKeysPos + i * viewConfig.micoStep; }) // .attr('x2', (d, i) => { // // 判断是否是最后五条线,如果是,则减少几厘米 // if (i >= horizontalData.length - 10) { // return viewConfig.width - viewConfig.marginLeft - viewConfig.marginRight // 向前缩短20像素 // } // return viewConfig.width - viewConfig.marginLeft - viewConfig.marginRight // 其他线使用默认长度 // }) .attr('x2', viewConfig.width - viewConfig.marginLeft - viewConfig.marginRight) .attr('fill', 'none') .attr('class', 'dataLine') .attr('stroke', viewConfig.stroke) .attr('stroke-width', 1) .attr('stroke-linejoin', viewConfig.strokeLinejoin) .attr('stroke-linecap', viewConfig.strokeLinecap) .attr('style', (d, i) => { if (i === horizontalData.length - 5) { return 'stroke-width: 1; stroke: black;'; // 不绘制最后一条线 } if (i % 5 === 0 && i !== 0) { return 'stroke-width: 3; stroke: blue;'; } return 'stroke-width: 1'; }); // 竖线绘制 const verticalData = [...new Array(42).keys()]; g.append('g') .selectAll('line') .data(verticalData) .join('line') .attr('x1', (d, i) => { return viewConfig.step + i * viewConfig.micoStep; }) .attr('y1', viewConfig.topKeysPos) .attr('x2', (d, i) => { return viewConfig.step + i * viewConfig.micoStep; }) .attr('y2', viewConfig.bottomKeysPosStart) .attr('fill', 'none') .attr('class', 'dataLine') .attr('stroke', viewConfig.stroke) .attr('stroke-width', 1) .attr('stroke-linejoin', viewConfig.strokeLinejoin) .attr('stroke-linecap', viewConfig.strokeLinecap); g.append('line') .attr('class', 'myline') .attr('x1', 0) .attr('y1', viewConfig.topKeysPos) .attr('x2', viewConfig.width - viewConfig.marginLeft - viewConfig.marginRight) .attr('y2', viewConfig.topKeysPos) .attr('stroke', viewConfig.stroke) .attr('stroke-width', 1) .attr('stroke-linejoin', viewConfig.strokeLinejoin) .attr('stroke-linecap', viewConfig.strokeLinecap); } // 绘制顶部数据 function drwaTopData(svg) { const g = getG(svg, viewConfig); // 绘制横向线条 g.selectAll('line') .data(TOP_KEYS) .join('line') .attr('x1', 0) .attr('y1', (d, i) => { return LINE_HEIGHT * (i + 1); }) .attr('y2', (d, i) => { return LINE_HEIGHT * (i + 1); }) .attr('x2', viewConfig.contentWidth) .attr('fill', 'none') .attr('class', 'newline') .attr('stroke', viewConfig.stroke) .attr('stroke-width', 1) .attr('stroke-linejoin', viewConfig.strokeLinejoin) .attr('stroke-linecap', viewConfig.strokeLinecap); // 数据的值 const repeatArr = [...new Array(8).keys()]; // 绘制文字 TOP_KEYS.map(({ getValue, name }, index) => { g.append('g') .selectAll('text') .data(repeatArr) .join('text') .attr('style', 'font-size:14px;text-anchor:middle;') .attr('class', 'mytext') .text((i) => { if (i === 0) { return name; } else { return getValue(i - 1, viewConfig.renderData); } }) .attr('x', (i) => i * viewConfig.step + viewConfig.step / 2) .attr('y', () => { return LINE_HEIGHT * (index + 1) - TEXT_MARGIN_BOTTOM; }); }); // 绘制时间字段 // 绘制汉字 g.append('text') .attr('style', 'font-size:14px;text-anchor:middle;') .text('时 间') .attr('x', viewConfig.step / 2) .attr('y', viewConfig.topKeysPos - TEXT_MARGIN_BOTTOM); // 绘制竖线和时间汉字 const data = new Array(timeNumber.length * 7) .fill('') .map((d, i) => timeNumber[i % timeNumber.length]); g.append('g') .selectAll('text') .data(data) .join('text') .attr('style', 'font-size:14px') .attr('class', 'mytext') .attr('fill', (d) => { if (nightTime.includes(d)) { return 'red'; } else { return viewConfig.stroke; } }) .text((d) => { return d; }) .attr('x', (d, i) => { return viewConfig.step + i * viewConfig.micoStep + 3; }) .attr('y', (d, i) => { return viewConfig.topKeysPos - TEXT_MARGIN_BOTTOM; }); // 线条 g.append('g') .attr('class', 'textYPos') .selectAll('line') .data(data) .join('line') .attr('x1', (d, i) => { return viewConfig.step + i * viewConfig.micoStep; }) .attr('y1', viewConfig.topKeysPos - LINE_HEIGHT) .attr('x2', (d, i) => { return viewConfig.step + i * viewConfig.micoStep; }) .attr('y2', viewConfig.topKeysPos) .attr('fill', 'none') .attr('class', 'dataLine') .attr('stroke', viewConfig.stroke) .attr('stroke-width', (d, i) => { return i % 6 ? 1 : 0; }) .attr('stroke-linejoin', viewConfig.strokeLinejoin) .attr('stroke-linecap', viewConfig.strokeLinecap); } // 获取折线区域的高度 // function utilsGetMicoPos (step, botpos) { // const micoStep = (step * 7) / 42 // 折线小格子的宽度 // const verticalLength = bodyTemperature[1] - bodyTemperature[0] + 1 // 根据体温来计算格子 // const verticalHeight = micoStep * 5 * verticalLength // return { // micoStep, // verticalHeight // } // } // 绘制折线 function brokenLine(svg, pathData) { const I = d3.map(pathData, (_, i) => i); const g = getG(svg, viewConfig); const line = d3 .line() .defined((i) => pathData[i].value) .x((i) => { return xScale(pathData[i].index) + viewConfig.X_OFFSET; }) .y((i) => bodyScale(pathData[i].value)); g.attr('class', 'body-line'); getDrawPath({ content: g, line: line(I.filter((i) => pathData[i].value)), }); } // 获取体温数据 function getIndexValue(list1, list2, list3, list4) { let list = []; list = list.concat(list1.filter((x) => x.value !== null)); list = list.concat(list2.filter((x) => x.value !== null)); list = list.concat(list3.filter((x) => x.value !== null)); list = list.concat(list4.filter((x) => x.value !== null)); list.sort((x, y) => x.index - y.index); return list.map((x) => { return { index: x.index, value: bodyScale(x.value), }; }); } // 绘制 疼痛曲线 function drawPain(svg, pathData) { const g = getG(svg, viewConfig); const I = d3.map(pathData, (_, i) => i); g.on( 'pointerenter pointermove', generatePointer({ pathData: viewConfig.renderData.painScore.flat(Infinity), type: '疼痛', yScaleInstance: heartScale, }) ).on('pointerleave', pointerleft); const line = d3 .line() .defined((i) => pathData[i].value || pathData[i].value === 0) .x((i) => { return xScale(pathData[i].index) + viewConfig.X_OFFSET; }) .y((i) => { if (pathData[i].value) { return painScale(pathData[i].value / 10) + 10; } return painScale(0) + 10; }); getDrawPath({ content: g, line: line(I.filter((i) => pathData[i].value || pathData[i].value === 0)), stroke: 'black', }); // iconDrawObj.getDrawRoundIcon({ // content: g, // data: d3.range(pathData.length), // x: (i) => { // return xScale(pathData[i].index) + viewConfig.X_OFFSET // }, // y: (i) => { // if (pathData[i].value) { // return painScale(pathData[i].value / 10) + 10 // } // return painScale(0) + 10 // }, // // riangle: 24, // fill: 'black', // stroke: 'black' // }) iconDrawObj.drawThreeIcon({ content: g, data: d3.range(pathData.length), x: (i) => { return xScale(pathData[i].index) + viewConfig.X_OFFSET; }, y: (i) => { if (pathData[i].value) { return painScale(pathData[i].value / 10) + 10; } return painScale(0) + 10; }, riangle: 24, }); } // 绘制脉搏 function drawHeart(svg, pathData, isEmpty) { const I = d3.map(pathData, (_, i) => i); const g = getG(svg, viewConfig); g.on( 'pointerenter pointermove', generatePointer({ pathData: viewConfig.renderData.datasetPulse.flat(Infinity), type: '脉搏', yScaleInstance: heartScale, }) ).on('pointerleave', pointerleft); const line = d3 .line() .defined((i) => pathData[i].value) .x((i) => { return xScale(pathData[i].index) + viewConfig.X_OFFSET; }) .y((i) => heartScale(pathData[i].value || 0)); if (isEmpty) { getDrawPath({ content: g, line: line(I.filter((i) => pathData[i].value)), stroke: 'red', }); } iconDrawObj.getDrawRoundIcon({ content: g, data: d3.range(pathData.length), x: (i) => { return xScale(pathData[i].index) + viewConfig.X_OFFSET; }, y: (i) => { return heartScale(pathData[i].value); }, fill: isEmpty ? 'transparent' : 'red', stroke: 'red', }); } // 绘制心率 function drawHeartRate(svg, pathData) { const I = d3.map(pathData, (_, i) => i); const g = getG(svg, viewConfig); g.on( 'pointerenter pointermove', generatePointer({ pathData: viewConfig.renderData.datasetHeartRate.flat(Infinity), type: '心率', yScaleInstance: heartScale, }) ).on('pointerleave', pointerleft); const line = d3 .line() .defined((i) => pathData[i].value) .x((i) => { return xScale(pathData[i].index) + viewConfig.X_OFFSET; }) .y((i) => heartScale(pathData[i].value || 0)); getDrawPath({ content: g, line: line(I.filter((i) => pathData[i].value)), stroke: 'red', }); iconDrawObj.getDrawRoundIcon({ content: g, data: d3.range(pathData.length), x: (i) => { return xScale(pathData[i].index) + viewConfig.X_OFFSET; }, y: (i) => { return heartScale(pathData[i].value); }, fill: 'white', stroke: 'red', }); } // 腋温 function drawAnus(svg, pathData) { const g = getG(svg, viewConfig); g.on( 'pointerenter pointermove', generatePointer({ pathData, type: '体温', yScaleInstance: bodyScale, }) ).on('pointerleave', pointerleft); iconDrawObj.getDrawXIcon({ content: g, data: d3.range(pathData.length), x: (i) => { return xScale(pathData[i].index) + viewConfig.X_OFFSET; }, y: (i) => bodyScale(pathData[i].value ? (pathData[i].value <= 35 ? 0 : pathData[i].value) : 0), }); } // 绘制一个线条 function getDrawPath({ content, line, stroke = 'blue' }) { content .append('path') .attr('class', 'mylines') .attr('fill', 'none') .attr('stroke', stroke) .attr('stroke-width', viewConfig.strokeWidth) .attr('stroke-linejoin', viewConfig.strokeLinejoin) .attr('stroke-linecap', viewConfig.strokeLinecap) .attr('d', line); } // // 绘制降温的红圈和虚线 // function drawCoolBody(svg, coolData, allData) { // console.log('绘制降温的红圈和虚线',svg, coolData, allData) // const g = getG(svg, viewConfig) // const lineData = []; // // 遍历data1中的每个对象 // allData.forEach(item1 => { // console.log('这应该有值吧',item1) // // 如果item1有index和date属性 // if (item1.index !== undefined && item1.date !== undefined) { // console.log('这应该有值吧',item1) // // 遍历data2中的每个对象 // coolData.forEach(item2 => { // // 如果item2也有index和date属性,并且它们与item1的相等 // if (item2.index === item1.index && item2.date === item1.date) { // // 将匹配的对象添加到结果数组中 // // 这里可以添加整个对象,或者根据需要添加对象的某些属性 // lineData.push({ ...item1, ...item2 }); // 使用展开运算符合并对象,注意这可能会覆盖相同属性的值 // // 如果不希望覆盖,可以只添加item1或item2,或者创建一个新对象包含所需属性 // } // }); // } // }); // console.log('绘制降温的红圈和虚线2222',lineData) // g.append('g') // .selectAll('line') // .data(lineData) // .join('line') // .attr('class', 'xuxianliane') // .attr('x1', function({ index,date,value }) { // return xScale(index) + viewConfig.X_OFFSET + (index - 20) / 4 // }) // .attr('y1', function({ value }) { // return bodyScale(value) // }) // .attr('x2', function({ index }) { // return xScale(index) + viewConfig.X_OFFSET + (index - 20) / 4 // }) // .attr('y2', function({ index,date }) { // const bodyValue = allData.filter(item => item.index === index && item.date === date )[0].value // return bodyScale(bodyValue) // }) // .attr('stroke', 'red') // .attr('stroke-width', 2) // .style('stroke-dasharray', '3, 3') // .attr('stroke-linejoin', viewConfig.strokeLinejoin) // .attr('stroke-linecap', viewConfig.strokeLinecap) // // 绘制icon // iconDrawObj.getDrawRoundIcon({ // content: g, // data: d3.range(lineData.length), // x: (i) => { // return xScale(lineData[i].index) + viewConfig.X_OFFSET + (lineData[i].index - 20) / 4 // }, // y: (i) => { // return bodyScale(lineData[i].value) // }, // fill: 'transparent', // stroke: 'red', // r: 6 // }) // } // 绘制降温的红圈和虚线 function drawCoolBody(svg, coolData, allData) { const g = getG(svg, viewConfig); // 获取同个位置记录的温度 const temArrMap = allData.reduce((data, items) => { items.map((item) => { if (item.value) { data[item.index] = item.value; } }); return data; }, {}); const vaildData = coolData.filter((item) => item.value); const lineData = vaildData.filter(({ index, value }) => temArrMap[index] !== value); g.append('g') .selectAll('line') .data(lineData) .join('line') .attr('class', 'xuxianliane') .attr('x1', function ({ index }) { return xScale(index) + viewConfig.X_OFFSET; }) .attr('y1', function ({ value }) { return bodyScale(value); }) .attr('x2', function ({ index }) { return xScale(index) + viewConfig.X_OFFSET; }) .attr('y2', function ({ index, value }) { const bodyValue = temArrMap[index]; let y2Location = bodyScale(value); if (bodyValue !== undefined) { y2Location = bodyScale(bodyValue); } return y2Location; }) .attr('stroke', 'red') .attr('stroke-width', 2) .style('stroke-dasharray', '3, 3') .attr('stroke-linejoin', viewConfig.strokeLinejoin) .attr('stroke-linecap', viewConfig.strokeLinecap); // 绘制icon iconDrawObj.getDrawRoundIcon({ content: g, data: d3.range(vaildData.length), x: (i) => { return xScale(vaildData[i].index) + viewConfig.X_OFFSET; }, y: (i) => { return bodyScale(vaildData[i].value); }, fill: 'transparent', stroke: 'red', r: 6, }); } // 绘制同一时间心率和脉搏的阴影 function drawRateBody(svg, pulseData, heartRateData) { if (pulseData[0].length > 0 || heartRateData[0].length > 0) { const g = getG(svg, viewConfig); const defs = g.append('defs'); const pattern = defs .append('pattern') .attr('id', 'diagonal-hatch') .attr('patternUnits', 'userSpaceOnUse') .attr('width', 1) .attr('height', 6) .attr('patternTransform', 'rotate(100)'); // 45度斜线 pattern .append('path') // .attr("d", "M-1,1 l2,-2 M0,4 l4,-4 M3,5 l2,-2") .attr('d', 'M-1,-1 l2,2 M0,0 l4,4 M3,3 l2,2') .attr('stroke', 'red') // 虚线颜色 .attr('stroke-width', 1) .attr('opacity', 0.8); // 透明度 // 创建一个映射,用于快速查找心率值 const heartRateMap = heartRateData[0].reduce((acc, curr) => { acc[curr.index] = curr.value; return acc; }, {}); // 创建一个映射,用于快速查找脉搏值 const pulseDataMap = pulseData[0].reduce((acc, curr) => { acc[curr.index] = curr.value; return acc; }, {}); if (Object.keys(heartRateMap).length >= 3 && Object.keys(pulseDataMap).length >= 3) { // 获取脉搏和心率的起点和终点 const pulseStart = pulseData[0][0]; // const pulseEnd = pulseData[0][pulseData[0].length - 1] const heartRateStart = heartRateMap[pulseStart.index]; // const heartRateEnd = heartRateMap[pulseEnd.index] // 生成路径数据 const pathData = pulseData[0].reduce((acc, { value, index }, i) => { const heartRateValue = heartRateMap[index]; // 过滤掉没有脉搏值和对应心率值的项 if ( value !== null && value !== undefined && heartRateValue !== null && heartRateValue !== undefined ) { const y1 = heartScale(value || 0); const x = xScale(index) + viewConfig.X_OFFSET; const y2 = heartScale(heartRateValue || 0); // 如果是第一个点,移动到起始点 if (acc === '') { acc += `M${x},${y1}`; // Move to the starting point } acc += `L${x},${y2}`; // Draw line to heart rate point } return acc; }, ''); // 生成闭合路径 let closingPath = pulseData[0].reduceRight((acc, { value, index }) => { const heartRateValue = heartRateMap[index]; if ( value !== null && value !== undefined && heartRateValue !== null && heartRateValue !== undefined ) { const x = xScale(index) + viewConfig.X_OFFSET; const y1 = heartScale(value || 0); // const y2 = heartScale(heartRateValue) acc += `L${x},${y1}`; // Draw line down to pulse point } return acc; }, '') + `L${xScale(pulseStart.index) + viewConfig.X_OFFSET},${heartScale(heartRateStart)}` + 'Z'; // Close the path closingPath += `L${xScale(pulseStart.index) + viewConfig.X_OFFSET},${heartScale( heartRateStart )}Z`; //绘制阴影区域 g.append('path') .attr('class', 'rate-shade') .attr('d', pathData + closingPath) // 完整的路径数据 .attr('fill', 'url(#diagonal-hatch)') // 使用图案填充 .attr('stroke', 'red'); // 设置边框颜色为红色 } } } // 绘制表格区域外面的信息数据 function drwaOtherInfo(svg, data) { const g = svg .append('g') .attr('transform', `translate(${viewConfig.marginLeft},${viewConfig.marginTop})`) .attr('style', 'font-size:14px;'); // 绘制第几周 getG(svg, viewConfig) .append('text') .attr('style', 'font-size:24px;') .attr('class', 'mytext') .text(`第${+data.weekNo + 1}周`) .attr('x', viewConfig.width / 2 - 40) .attr('y', viewConfig.bottomPos + TEXT_MARGIN_BOTTOM + 1.5 * LINE_HEIGHT - 25); // 绘制标注 getG(svg, viewConfig) .attr( 'transform', `translate(${viewConfig.marginLeft},${ viewConfig.bottomPos + viewConfig.marginTop + HEAD_HEIGHT })` ) .attr('style', 'font-size:14px;') .call((g) => { const dataList = [ { name: '体温', fn: iconDrawObj.getDrawXIcon, params: { content: g, data: [0], }, }, // { // name: '口温', // fn: iconDrawObj.getDrawRoundIcon, // params: { // content: g, // data: [0] // } // }, // { // name: '肛温', // fn: iconDrawObj.getDrawRoundDotIcon, // params: { // content: g, // data: [0] // } // }, // { // name: '耳温', // fn: iconDrawObj.drawThreeIcon, // params: { // content: g, // data: [0], // fill: 'white' // } // }, { name: '心率', fn: iconDrawObj.getDrawRoundIcon, params: { content: g, data: [0], fill: 'white', stroke: 'red', }, }, { name: '脉搏', fn: iconDrawObj.getDrawRoundIcon, params: { content: g, data: [0], fill: 'red', stroke: 'red', }, }, ]; g.append('text').text('标注:'); dataList.map((item, i) => { g.append('text') .text(item.name) .attr('x', 40 + i * 80); item.fn({ ...item.params, x: () => 80 + i * 80, y: () => -4, }); }); }) .attr('x', viewConfig.width / 2 - 40) .attr('y', viewConfig.bottomPos + TEXT_MARGIN_BOTTOM); // 绘制顶部的信息数据 g.append('text') .attr('class', 'mytext') .attr('x', 0) .attr('y', () => { return HEAD_HEIGHT - TEXT_MARGIN_BOTTOM - 6; }) .html(() => { return INFO_KEYS.map(({ name, key }, index) => { return `${name}: ${ data[key] ? data[key] : '' }`; }).join(''); }); // 绘制标题 g.append('text') .attr('style', 'font-size:22px;text-anchor: middle;') .attr('class', 'mytext') .attr('x', viewConfig.width / 2) .attr('y', () => { return HEAD_HEIGHT - 4 * LINE_HEIGHT; }) .text(Header.HospitalName); // 体温单 g.append('text') .attr('style', 'font-size:22px;text-anchor: middle;') .attr('class', 'mytext') .attr('x', viewConfig.width / 2) .attr('y', () => { return HEAD_HEIGHT - 2.5 * LINE_HEIGHT; }) .text(Header.temperatureName); } function generatePointer({ pathData, type, yScaleInstance }) { return (event) => { var index = Math.round( (d3.pointer(event)[0] - viewConfig.step - textLeftMargin) / xScale.step() ); var val = xScale.domain()[index]; // const i = d3.bisectCenter(d3.range(pathData.length), val); let i = -1; pathData.forEach((item, index) => { if (item.index === val) { i = index; } }); const yPos = yScaleInstance(+pathData[i].value) + viewConfig.marginTop + HEAD_HEIGHT; tooltip.style('display', null); tooltip.attr('class', 'myTooltip'); tooltip.attr('transform', `translate(${xScale(val) + viewConfig.micoStep},${yPos})`); const path = tooltip .selectAll('path') .data(['', '']) .join('path') .attr('fill', 'white') .attr('stroke', 'black'); const text = tooltip .selectAll('text') .data(['', '']) .join('text') .call((text) => text .selectAll('tspan') .data([`${type}: ${pathData[i].value}`, `${pathData[i].date}`]) .join('tspan') .attr('x', 0) .attr('y', (_, i) => `${i * 1.2}em`) .attr('font-weight', (_, i) => (i ? null : 'bold')) .text((d) => d) ); const { y, width: w, height: h } = text.node().getBBox(); text.attr('transform', `translate(${-w / 2},${15 - y})`); path.attr('d', `M${-w / 2 - 10},5H-5l5,-5l5,5H${w / 2 + 10}v${h + 20}h-${w + 20}z`); }; } function pointerleft() { tooltip.style('display', 'none'); } } function getTypeValue(type, allData = [], isNumber = true) { return allData.filter((item) => item.typeCode === type || ''); }