You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
385 lines
14 KiB
385 lines
14 KiB
2 months ago
|
/**
|
||
|
* 产程图包括上下两部分,通过两个不同的svg来展示。
|
||
|
*/
|
||
|
class PartoGram {
|
||
|
constructor(args){
|
||
|
//上半部分Svg
|
||
|
this.topSvg = args.topSvg;
|
||
|
//下半部分Svg
|
||
|
this.bottomSvg = args.bottomSvg;
|
||
|
//svg左边距
|
||
|
this.marginLeft = args.marginLeft;
|
||
|
//svg上边距
|
||
|
this.marginTop = args.marginTop;
|
||
|
//svg右边距
|
||
|
this.marginRight = args.marginRight||40;
|
||
|
//svg下边距
|
||
|
this.marginButtom = args.marginButtom||40;
|
||
|
//d3.domain定义域 X轴
|
||
|
this.Domain_X = args.Domain_X||[0,24];
|
||
|
//上部svg的左边y坐标轴定义域
|
||
|
this.domain_TYL = args.domain_TYL||[0, 10];
|
||
|
//上部svg的右边y坐标轴定义域
|
||
|
this.domain_TYR = args.domain_TYR||[-5, 5];
|
||
|
//下部svg的右边y坐标轴定义域
|
||
|
this.domain_BYL = args.domain_BYL||
|
||
|
['时间','胎心','血压','宫缩','处理','签名','评分','小结','备注',''];
|
||
|
this.domain_scale =[0,1,2,3,4,9,11,12,15,16,17];
|
||
|
this.yCutOut = this.yCutOut||11
|
||
|
//请求后台数据地址
|
||
|
this.url = args.url;
|
||
|
this._scaleTopSvgX = null;//x轴的坐标标尺
|
||
|
this._scaleTopSvgLeftY = null//topSvg左边的Y轴坐标尺
|
||
|
this._scaleTopSvgRightY = null//topSvg右边的Y轴坐标尺
|
||
|
this._scaleBottomSvgY = null;//bottomSvgY轴坐标尺
|
||
|
this._scaleBottomSvgX = null;//bottomSvgX轴坐标尺
|
||
|
this.run();
|
||
|
|
||
|
}
|
||
|
run(){
|
||
|
this.initTopSvgXaxris();
|
||
|
this.initTopSvgY1axris();
|
||
|
this.initTopSvgY2axris();
|
||
|
//下部svg
|
||
|
this.initBottomSvgAxris();
|
||
|
|
||
|
}
|
||
|
|
||
|
initTopSvgXaxris(){
|
||
|
//横坐标的最大长度
|
||
|
let topsvg = d3.select("#"+this.topSvg.id);
|
||
|
let xScaleMax = topsvg.attr("width")-this.marginRight
|
||
|
let xScale = d3.scaleLinear()
|
||
|
//输入范围
|
||
|
.domain(this.Domain_X)
|
||
|
//输出范围
|
||
|
.range([this.marginLeft, xScaleMax]);
|
||
|
this._scaleTopSvgX=xScale;//设置topSvgX轴坐标尺
|
||
|
//纵坐标网格线长度
|
||
|
let xAxisLength = (topsvg.attr("height")-this.marginButtom-this.marginTop)
|
||
|
let xAxis = d3.axisBottom(xScale)
|
||
|
.ticks(this.Domain_X[this.Domain_X.length-1]-this.Domain_X[0]).tickSize(-xAxisLength);
|
||
|
|
||
|
//横坐标在Y方向的偏移量
|
||
|
let yOffest= topsvg.attr("height")-this.marginButtom;
|
||
|
topsvg.append("g").call(xAxis)
|
||
|
.call(this.tasf,0,yOffest);
|
||
|
}
|
||
|
|
||
|
initTopSvgY1axris(){
|
||
|
let topsvg = d3.select("#"+this.topSvg.id);
|
||
|
var range_y = topsvg.attr("height") - this.marginButtom-this.marginTop;
|
||
|
let yScale1 = d3.scaleLinear()
|
||
|
.domain(this.domain_TYL)
|
||
|
.range([topsvg.attr("height") - this.marginButtom, this.marginTop]);
|
||
|
this._scaleTopSvgLeftY = yScale1;
|
||
|
let yAxisLength = (topsvg.attr("width")-this.marginLeft-this.marginRight)
|
||
|
let yAxis1 = d3.axisLeft().scale(yScale1).tickSize(-yAxisLength);
|
||
|
|
||
|
topsvg.append("g")
|
||
|
.call(yAxis1)
|
||
|
.call(this.tasR,90)
|
||
|
.call(this.tasf,this.marginLeft,0);
|
||
|
topsvg.append("g").call(this.textMkr,"宫颈扩张(厘米)红色",this.marginLeft-30,this.marginTop+(range_y/2),"label_lr");
|
||
|
topsvg.append("g").call(this.textMkr,"胎头下降(厘米)蓝色",topsvg.attr("width")-10,this.marginTop+(range_y/2),"label_lr");
|
||
|
|
||
|
|
||
|
}
|
||
|
initTopSvgY2axris(){
|
||
|
let topsvg = d3.select("#"+this.topSvg.id);
|
||
|
let yScale2 = d3.scaleLinear()
|
||
|
.domain(this.domain_TYR)
|
||
|
.range([topsvg.attr("height")- this.marginButtom, this.marginTop]);
|
||
|
this._scaleTopSvgRightY = yScale2;
|
||
|
let yAxis2 = d3.axisRight(yScale2).tickSize(0);
|
||
|
|
||
|
topsvg.append("g")
|
||
|
.call(yAxis2)
|
||
|
.call(this.tasf,topsvg.attr("width")-this.marginRight,0);
|
||
|
|
||
|
|
||
|
}
|
||
|
//初始化下边的svg的y坐标轴
|
||
|
initBottomSvgAxris(){
|
||
|
let bottomsvg = d3.select("#"+this.bottomSvg.id);
|
||
|
let yMax = this.domain_scale[this.domain_scale.length-1];
|
||
|
let ySourceDomainL = [0,yMax];
|
||
|
let yTargetDomain
|
||
|
= [0,bottomsvg.attr("height")-this.marginButtom];
|
||
|
let y2Scale = d3.scaleLinear()
|
||
|
.domain(ySourceDomainL)
|
||
|
.range(yTargetDomain);
|
||
|
this._scaleBottomSvgY = y2Scale;
|
||
|
let yAxisLength = (bottomsvg.attr("width")-this.marginLeft-this.marginRight)
|
||
|
let y2Axis = d3.axisLeft(y2Scale).tickSize(-yAxisLength).tickValues(this.domain_scale)
|
||
|
.tickFormat("");//自定义刻度;
|
||
|
|
||
|
bottomsvg.append("g")
|
||
|
.call(y2Axis)
|
||
|
.call(this.tasf,this.marginLeft,0)
|
||
|
|
||
|
|
||
|
//横坐标的最大长度
|
||
|
let xScaleMax = bottomsvg.attr("width")-this.marginRight;
|
||
|
let xScale = d3.scaleLinear()
|
||
|
//输入范围
|
||
|
.domain(this.Domain_X)
|
||
|
//输出范围
|
||
|
.range([this.marginLeft, xScaleMax]);
|
||
|
this._scaleBottomSvgX = xScale;
|
||
|
//纵坐标网格线长度
|
||
|
let xAxisLength = (bottomsvg.attr("height")-this.marginButtom)
|
||
|
let xAxis = d3.axisTop(xScale)
|
||
|
.ticks(this.Domain_X[this.Domain_X.length-1]-this.Domain_X[0]).tickSize(-xAxisLength*this.yCutOut/yMax).tickSizeOuter(-xAxisLength);
|
||
|
|
||
|
|
||
|
//横坐标在Y方向的偏移量
|
||
|
bottomsvg.append("g").call(xAxis);
|
||
|
//画最后一行的竖线
|
||
|
var x_range = this.Domain_X[this.Domain_X.length-1]
|
||
|
var df = Math.round(x_range/3);//
|
||
|
var ds =[[df,yMax-1],[df,yMax]];
|
||
|
this.lineMkr(xScale,y2Scale,bottomsvg,ds);
|
||
|
ds =[[df*2,yMax-1],[df*2,yMax]];
|
||
|
this.lineMkr(xScale,y2Scale,bottomsvg,ds);
|
||
|
var yMax_range = bottomsvg.attr("height")-this.marginButtom;
|
||
|
let minimum_sc = yMax_range/yMax
|
||
|
bottomsvg.append("g").call(this.textMkr,"时间",this.marginLeft-20,8+minimum_sc-minimum_sc/2,"label_tb");
|
||
|
bottomsvg.append("g").call(this.textMkr,"胎心",this.marginLeft-20,8+minimum_sc*2-minimum_sc/2,"label_tb");
|
||
|
bottomsvg.append("g").call(this.textMkr,"血压",this.marginLeft-20,8+minimum_sc*3-minimum_sc/2,"label_tb");
|
||
|
bottomsvg.append("g").call(this.textMkr,"宫缩",this.marginLeft-20,8+minimum_sc*4-minimum_sc/2,"label_tb");
|
||
|
bottomsvg.append("g").call(this.textMkr,"处",this.marginLeft-20,8+minimum_sc*5-minimum_sc/2,"label_tb");
|
||
|
bottomsvg.append("g").call(this.textMkr,"理",this.marginLeft-20,8+minimum_sc*7-minimum_sc/2,"label_tb");
|
||
|
bottomsvg.append("g").call(this.textMkr,"签",this.marginLeft-20,8+minimum_sc*10-minimum_sc/2,"label_tb");
|
||
|
bottomsvg.append("g").call(this.textMkr,"名",this.marginLeft-20,8+minimum_sc*11-minimum_sc/2,"label_tb");
|
||
|
bottomsvg.append("g").call(this.textMkr,"评分",this.marginLeft-20,8+minimum_sc*12-minimum_sc/2,"label_tb");
|
||
|
bottomsvg.append("g").call(this.textMkr,"小结",this.marginLeft-20,8+minimum_sc*14-minimum_sc/2,"label_tb");
|
||
|
bottomsvg.append("g").call(this.textMkr,"备注",this.marginLeft-20,8+minimum_sc*16-minimum_sc/2,"label_tb");
|
||
|
var ys_xy =[[0,yMax]];
|
||
|
ys_xy = ys_xy.map(d => [ xScale(d[0]), y2Scale(d[1]) ] );
|
||
|
bottomsvg.append("g")
|
||
|
.selectAll("#ys")
|
||
|
.data(ys_xy).enter()
|
||
|
.call(this.textTransMkr,"医生:",d => d[0],d => d[1]-12);
|
||
|
var jcz_xy =[[df,yMax]]
|
||
|
jcz_xy =jcz_xy.map(d => [ xScale(d[0]), y2Scale(d[1]) ] );
|
||
|
bottomsvg.append("g")
|
||
|
.selectAll("#jcz")
|
||
|
.data(jcz_xy).enter()
|
||
|
.call(this.textTransMkr,"接产者:",d => d[0],d => d[1]-12);
|
||
|
var xh_xy =[[df*2,yMax]]
|
||
|
xh_xy =xh_xy.map(d => [ xScale(d[0]), y2Scale(d[1]) ] );
|
||
|
bottomsvg.append("g")
|
||
|
.selectAll("#xhys")
|
||
|
.data(xh_xy).enter()
|
||
|
.call(this.textTransMkr,"巡回:",d => d[0],d => d[1]-12);
|
||
|
|
||
|
|
||
|
|
||
|
//画斜线,血压
|
||
|
var domain_X = this.Domain_X;
|
||
|
var domain_Y = this.domain_scale;
|
||
|
let bp_Y1 = domain_Y[3];
|
||
|
let bp_Y2 = domain_Y[2];
|
||
|
for(let i =domain_X[0];i<domain_X[domain_X.length-1];i++){
|
||
|
var ds =[[i,bp_Y1],[i+1,bp_Y2]];
|
||
|
this.lineMkr(xScale,y2Scale,bottomsvg,ds);
|
||
|
}
|
||
|
//宫缩
|
||
|
let uc_Y1 = domain_Y[2];
|
||
|
let uc_Y2 = domain_Y[1];
|
||
|
for(let i =domain_X[0];i<domain_X[domain_X.length-1];i++){
|
||
|
var ds =[[i,uc_Y1],[i+1,uc_Y2]];
|
||
|
this.lineMkr(xScale,y2Scale,bottomsvg,ds);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @param {x坐标系} xScale
|
||
|
* @param {y坐标系} y2Scale
|
||
|
* @param {选择集} sel
|
||
|
* @param {数据数组} ds
|
||
|
*/
|
||
|
lineMkr(xScale,yScale,sel,ds){
|
||
|
ds = ds.map(d =>[ xScale(d[0]), yScale(d[1])]);
|
||
|
let lnMkr = d3.line();
|
||
|
sel.append("g")
|
||
|
.append("path")
|
||
|
.attr("d",lnMkr(ds))
|
||
|
.attr("fill","none")
|
||
|
.attr("stroke","red");
|
||
|
}
|
||
|
|
||
|
|
||
|
textMkr(sel,label,x,y,style){
|
||
|
let label_ = d3.local(),dsX=d3.local(),dsY =d3.local();
|
||
|
sel.each(function(d ,i){
|
||
|
label_.set(this,typeof label==="function"?label(d,i):label||0)
|
||
|
});
|
||
|
sel.each(function(d ,i){
|
||
|
dsX.set(this,typeof x==="function"?x(d,i):x||0)
|
||
|
});
|
||
|
sel.each(function(d ,i){
|
||
|
dsY.set(this,typeof y==="function"?y(d,i):y||0)
|
||
|
});
|
||
|
|
||
|
sel.append("text")
|
||
|
.attr("x",function(){return dsX.get(this)}||0)
|
||
|
.attr("y",function(){return dsY.get(this)}||5)
|
||
|
.classed(style||"label_tb",true)
|
||
|
.text(function(){return label_.get(this)});
|
||
|
}
|
||
|
|
||
|
textTransMkr(sel,label,x,y){
|
||
|
sel.append("text")
|
||
|
.attr("x",x||0)
|
||
|
.attr("y",y||5)
|
||
|
.attr("transform","translate(5,-2)")
|
||
|
.text(label?label:d=>d);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//transform的公用方法
|
||
|
tasf(sel,dx,dy){
|
||
|
var dxs=d3.local(), dys=d3.local();
|
||
|
sel.each(function(d ,i){
|
||
|
dxs.set(this,typeof dx==="function"?dx(d,i):dx||0)
|
||
|
});
|
||
|
sel.each(function(d,i){
|
||
|
dys.set(this,typeof dy==="function"?dy(d,i):dy||0)
|
||
|
});
|
||
|
return sel.attr("transform",function(){
|
||
|
return "translate("+dxs.get(this)+","+dys.get(this)+")"
|
||
|
})
|
||
|
}
|
||
|
//旋转角度
|
||
|
tasR(sel,r){
|
||
|
var dr = d3.local();
|
||
|
sel.each(function(d ,i){
|
||
|
dr.set(this,typeof r==="function"?r(d,i):r||0)
|
||
|
});
|
||
|
return sel.attr("transform",function(){
|
||
|
return "rotate("+dr.get(this)+")"
|
||
|
})
|
||
|
}
|
||
|
//对外接口-----begin
|
||
|
setBottomTextStr(ds,x,y,xOffset=0.1,yOffest=-0.1,style){
|
||
|
let xSc = this._scaleBottomSvgX ;//bottomSvgX轴坐标尺
|
||
|
let ySc = this._scaleBottomSvgY ;//bottomSvgY轴坐标尺
|
||
|
let svg = d3.select("#"+this.bottomSvg.id);
|
||
|
svg.append("g")
|
||
|
.append("text")
|
||
|
.classed(style,true)
|
||
|
.attr("x",xSc(x+xOffset))
|
||
|
.attr("y",ySc(y+yOffest))
|
||
|
.text(ds);
|
||
|
}
|
||
|
//设置普通文本,通过svg来画
|
||
|
setText(id,ds,x,y,style){
|
||
|
let svg = d3.select("#"+id);
|
||
|
svg.append("g")
|
||
|
.append("text")
|
||
|
.classed(style,true)
|
||
|
.attr("x",x)
|
||
|
.attr("y",y)
|
||
|
.text(ds);
|
||
|
}
|
||
|
//
|
||
|
/**
|
||
|
* 宫颈扩张
|
||
|
* @param {ds格式必须是[[0,1],[1,2]]二维数组} ds
|
||
|
* @param {点和线的颜色} color
|
||
|
* @param {点的半径} r
|
||
|
*/
|
||
|
setDilatationCervix(ds,color,r){
|
||
|
let len = ds.length;
|
||
|
var resDs = []
|
||
|
for(let i =0;i<len;i++){
|
||
|
//新数组,x坐标,y坐标,坐标上的值
|
||
|
let newArr =[i,ds[i]];
|
||
|
resDs.push(newArr);
|
||
|
}
|
||
|
let xSc = this._scaleTopSvgX;
|
||
|
let ySc = this._scaleTopSvgLeftY;
|
||
|
resDs = resDs.map(d => [ xSc(d[0]), ySc(d[1]) ] );
|
||
|
let topsvg = d3.select("#"+this.topSvg.id);
|
||
|
topsvg.append("g")
|
||
|
.selectAll("circle")
|
||
|
.data(resDs).enter()
|
||
|
.append("circle").attr("r",r||4)
|
||
|
.attr("cx",d =>d[0]).attr("cy",d => d[1])
|
||
|
.style("fill",color||"blue")
|
||
|
// .style("stroke", "red"); // 8
|
||
|
var lnMaker = d3.line();
|
||
|
topsvg.append("g").append("path")
|
||
|
.attr("d",lnMaker(resDs))
|
||
|
.attr("fill","none")
|
||
|
.attr("stroke",color||"blue");
|
||
|
}
|
||
|
/**
|
||
|
* 胎头下降
|
||
|
@param {ds格式必须是[[0,1],[1,2]]二维数组} ds
|
||
|
* @param {点和线的颜色} color
|
||
|
* @param {点的半径} r
|
||
|
*/
|
||
|
setFetalHeadDescent(ds,color,r){
|
||
|
let len = ds.length;
|
||
|
var resDs = []
|
||
|
for(let i =0;i<len;i++){
|
||
|
//新数组,x坐标,y坐标,坐标上的值
|
||
|
let newArr =[i,ds[i]];
|
||
|
resDs.push(newArr);
|
||
|
}
|
||
|
let xSc = this._scaleTopSvgX;
|
||
|
let ySc = this._scaleTopSvgRightY;
|
||
|
resDs = resDs.map(d => [ xSc(d[0]), ySc(d[1]) ] );
|
||
|
let topsvg = d3.select("#"+this.topSvg.id);
|
||
|
topsvg.append("g")
|
||
|
.selectAll("circle")
|
||
|
.data(resDs).enter()
|
||
|
.append("circle").attr("r",r||4)
|
||
|
.attr("cx",d =>d[0]).attr("cy",d => d[1])
|
||
|
.style("fill",color||"red")
|
||
|
// .style("stroke", "red"); // 8
|
||
|
var lnMaker = d3.line();
|
||
|
topsvg.append("g").append("path")
|
||
|
.attr("d",lnMaker(resDs))
|
||
|
.attr("fill","none")
|
||
|
.attr("stroke",color||"red")
|
||
|
.attr("stroke-dasharray","5,5")
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @param {数据来源,按一定顺序的一维数组} ds
|
||
|
* @param {在y轴的那个地方显示} y_axis
|
||
|
* @param {偏差} offset
|
||
|
* @param {文本样式} style
|
||
|
*/
|
||
|
setTextData (ds,y_axis,xOffset,yOffset,style){
|
||
|
//把一维数组转化为二维数组
|
||
|
let len = ds.length;
|
||
|
var resDs = []
|
||
|
for(let i =0;i<len;i++){
|
||
|
//新数组,x坐标,y坐标,坐标上的值
|
||
|
let newArr =[i+xOffset,y_axis+yOffset,ds[i]];
|
||
|
resDs.push(newArr);
|
||
|
}
|
||
|
let bottomsvg = d3.select("#"+this.bottomSvg.id);
|
||
|
let scaleX = this._scaleBottomSvgX;
|
||
|
let scaleY = this._scaleBottomSvgY;
|
||
|
let scaleXy = resDs.map(d=>[scaleX(d[0]),scaleY(d[1]),d[2]])
|
||
|
bottomsvg.append("g")
|
||
|
.selectAll("#data")
|
||
|
.data(scaleXy).enter()
|
||
|
.append("text")
|
||
|
.attr("x",d =>d[0])
|
||
|
.attr("y",d =>d[1])
|
||
|
.classed(style,true)
|
||
|
.text(d =>d[2]);
|
||
|
|
||
|
}
|
||
|
}
|