@rfkit/renderer 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/index.js +1 -1
- package/package.json +1 -1
- package/renderers/circular/CircularBase.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ heatmapGL.render(largeMatrix); // largeMatrix: number[][]
|
|
|
87
87
|
- `IQ`/`IQEye` 接收 `{ IData: number[], QData: number[] }`。
|
|
88
88
|
`IQEye` 额外支持 `iqEyeMode`(`'classic' | 'legacy' | 'modern'`),默认 `classic`:每帧重算、绿红显影;传 `modern` 可启用跨帧 decay 模式。
|
|
89
89
|
- `Dial`/`Radar` 使用 `CircularData`;`Gauge` 使用 `{ value: number; limit?: number }`。
|
|
90
|
-
`CircularData` 支持可选 `polygon: CircularPolygonItem[]` 绘制雷达多边形:每个多边形提供 `values`(各方向归一化值 0-1,长度即方向数,按 0..360 度均匀分布)与可选 `color
|
|
90
|
+
`CircularData` 支持可选 `polygon: CircularPolygonItem[]` 绘制雷达多边形:每个多边形提供 `values`(各方向归一化值 0-1,长度即方向数,按 0..360 度均匀分布)与可选 `color`(缺省走主题主色);各方向值决定半径,连成闭合多边形,径向渐变填充(中心透明→边缘主题色)+ 细描边 + 顶点实心圆点。支持多网叠加,绘制时按每网最大半径降序(大网下层、小网上层)。
|
|
91
91
|
|
|
92
92
|
常用操作:`setRange(range)` 调整量程并重绘;`resize()` 在容器尺寸变化后调用;`clear()` 清空画布及内部数据。
|
|
93
93
|
|
package/index.js
CHANGED
|
@@ -43,4 +43,4 @@ void main() {
|
|
|
43
43
|
// 从颜色查找纹理获取颜色
|
|
44
44
|
fragColor = texture(u_colorTexture, vec2(normalized, 0.5));
|
|
45
45
|
}
|
|
46
|
-
`;class HeatmapWebGL extends WebGLEngine{init(props){super.init(props);const{gl}=this.state;if(!gl)return;const colorsInput=props.colors;const colors=colorsInput&&colorsInput.length>0?colorsInput:DEFAULTS.FLUORESCENCE_COLORS;const program=this.createProgram({vertex:VERTEX_SHADER,fragment:FRAGMENT_SHADER});const quadBuffer=this.createQuadBuffer();const dataTexture=this.createTexture();const colorTexture=this.createTexture();const maxTextureSize=gl.getParameter(gl.MAX_TEXTURE_SIZE);if(dataTexture){gl.activeTexture(gl.TEXTURE0);gl.bindTexture(gl.TEXTURE_2D,dataTexture);const emptyData=new Float32Array([INVALID_VALUE]);gl.texImage2D(gl.TEXTURE_2D,0,gl.R32F,1,1,0,gl.RED,gl.FLOAT,emptyData)}const range=props.range??DEFAULTS.RANGE;const[rangeMin,rangeMax]=range;const CI=new ColorInterpolator(colors,rangeMin,rangeMax,.1);this.updateProps({data:[],colors,program,quadBuffer,dataTexture,dataTextures:dataTexture?[dataTexture]:[],dataTiles:[],colorTexture,dataWidth:0,dataHeight:0,CI,maxTextureSize});gl.enable(gl.BLEND);gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);this.updateColorTexture();gl.clearColor(0,0,0,0);gl.clear(gl.COLOR_BUFFER_BIT);this.resize()}updateColorTexture(){const{gl,colorTexture,CI,range}=this.state;if(!gl||!colorTexture||!CI)return;const[rangeMin,rangeMax]=range;const size=256;const colorData=new Uint8Array(4*size);for(let i=0;i<size;i++){const value=rangeMin+i/(size-1)*(rangeMax-rangeMin);const color=CI.getColor(value);colorData[4*i]=color.r;colorData[4*i+1]=color.g;colorData[4*i+2]=color.b;colorData[4*i+3]=color.a}gl.activeTexture(gl.TEXTURE1);gl.bindTexture(gl.TEXTURE_2D,colorTexture);gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,size,1,0,gl.RGBA,gl.UNSIGNED_BYTE,colorData)}updateDataTexture(){const{gl,data}=this.state;if(!gl||0===data.length)return;const height=data.length;let width=0;for(let row=0;row<height;row++){const rowLen=data[row]?.length??0;if(rowLen>width)width=rowLen}if(0===width)return;const maxSize=this.state.maxTextureSize||gl.getParameter(gl.MAX_TEXTURE_SIZE);if(!maxSize||maxSize<=0)return;const tilesX=Math.ceil(width/maxSize);const tilesY=Math.ceil(height/maxSize);const nextTileCount=tilesX*tilesY;let dataTextures=this.state.dataTextures??[];if(dataTextures.length<nextTileCount){const missing=nextTileCount-dataTextures.length;for(let i=0;i<missing;i++){const tex=this.createTexture();if(tex)dataTextures.push(tex)}}else if(dataTextures.length>nextTileCount){for(let i=nextTileCount;i<dataTextures.length;i++)gl.deleteTexture(dataTextures[i]);dataTextures=dataTextures.slice(0,nextTileCount)}const dataTiles=[];gl.activeTexture(gl.TEXTURE0);for(let ty=0;ty<tilesY;ty++){const startRow=ty*maxSize;const tileH=Math.min(maxSize,height-startRow);for(let tx=0;tx<tilesX;tx++){const startCol=tx*maxSize;const tileW=Math.min(maxSize,width-startCol);const tileIndex=ty*tilesX+tx;const texture=dataTextures[tileIndex];if(!texture)continue;const floatData=new Float32Array(tileW*tileH);for(let localRow=0;localRow<tileH;localRow++){const globalRow=startRow+localRow;const rowData=data[globalRow];const rowLen=rowData?.length??0;const rowOffset=localRow*tileW;for(let localCol=0;localCol<tileW;localCol++){const globalCol=startCol+localCol;if(0===rowLen||globalCol>=rowLen){floatData[rowOffset+localCol]=INVALID_VALUE;continue}const value=rowData[globalCol];if(void 0===value||Number.isNaN(value))floatData[rowOffset+localCol]=INVALID_VALUE;else floatData[rowOffset+localCol]=value}}gl.bindTexture(gl.TEXTURE_2D,texture);gl.texImage2D(gl.TEXTURE_2D,0,gl.R32F,tileW,tileH,0,gl.RED,gl.FLOAT,floatData);dataTiles[tileIndex]={startCol,startRow,width:tileW,height:tileH}}}this.updateProps({dataTexture:dataTextures[0]??null,dataTextures,dataTiles,dataWidth:width,dataHeight:height})}updateProps(e){super.updateProps(e);const colors=e.colors??this.state.colors;const range=e.range??this.state.range;if((e.colors||e.range)&&colors.length>0){const[rangeMin,rangeMax]=range;if(this.state.CI)this.state.CI.setColors(colors,rangeMin,rangeMax,.1);else this.state.CI=new ColorInterpolator(colors,rangeMin,rangeMax,.1);this.updateColorTexture()}}setRange(range){if(!range)return;this.updateProps({range});this.draw()}clear(){this.clearRect();this.updateProps({data:[]})}render(data){if(data?.length>0){this.state.data=data;this.updateDataTexture();this.draw()}}draw(){const{gl,program,quadBuffer,dataTextures,dataTiles,colorTexture,data,range,dataWidth,dataHeight,canvas}=this.state;if(!gl||!program||!quadBuffer||!colorTexture)return;gl.clearColor(0,0,0,0);gl.clear(gl.COLOR_BUFFER_BIT);if(0===data.length||0===dataWidth||0===dataHeight||0===dataTiles.length||0===dataTextures.length)return;const[rangeMin,rangeMax]=range;gl.useProgram(program);gl.bindBuffer(gl.ARRAY_BUFFER,quadBuffer);const positionLoc=gl.getAttribLocation(program,"a_position");gl.enableVertexAttribArray(positionLoc);gl.vertexAttribPointer(positionLoc,2,gl.FLOAT,false,0,0);const dataTextureLoc=gl.getUniformLocation(program,"u_dataTexture");const colorTextureLoc=gl.getUniformLocation(program,"u_colorTexture");const rangeMinLoc=gl.getUniformLocation(program,"u_rangeMin");const rangeMaxLoc=gl.getUniformLocation(program,"u_rangeMax");if(!dataTextureLoc||!colorTextureLoc||!rangeMinLoc||!rangeMaxLoc)return;gl.activeTexture(gl.TEXTURE1);gl.bindTexture(gl.TEXTURE_2D,colorTexture);gl.uniform1i(colorTextureLoc,1);gl.uniform1f(rangeMinLoc,rangeMin);gl.uniform1f(rangeMaxLoc,rangeMax);const canvasWidth=canvas.width;const canvasHeight=canvas.height;const toPixX=col=>Math.round(col/dataWidth*canvasWidth);const toPixYFromTop=row=>Math.round(row/dataHeight*canvasHeight);for(let i=0;i<dataTiles.length;i++){const tile=dataTiles[i];const texture=dataTextures[i];if(!tile||!texture)continue;const x0=toPixX(tile.startCol);const x1=toPixX(tile.startCol+tile.width);const yTop0=toPixYFromTop(tile.startRow);const yTop1=toPixYFromTop(tile.startRow+tile.height);const y0=canvasHeight-yTop1;const y1=canvasHeight-yTop0;const w=x1-x0;const h=y1-y0;if(!(w<=0)&&!(h<=0)){gl.viewport(x0,y0,w,h);gl.activeTexture(gl.TEXTURE0);gl.bindTexture(gl.TEXTURE_2D,texture);gl.uniform1i(dataTextureLoc,0);gl.drawArrays(gl.TRIANGLES,0,6)}}gl.viewport(0,0,canvasWidth,canvasHeight)}resize(){super.resize()}dispose(){const{gl,program,quadBuffer,dataTextures,colorTexture}=this.state;if(gl){if(program)gl.deleteProgram(program);if(quadBuffer)gl.deleteBuffer(quadBuffer);for(const tex of dataTextures)gl.deleteTexture(tex);if(colorTexture)gl.deleteTexture(colorTexture)}super.dispose()}}class HeatmapCanvas extends Engine{init(props){super.init(props);const{colors}=props;this.updateProps({colors,data:[]});this.resize()}updateProps(e){super.updateProps(e);const{range=this.state.range,colors=this.state.colors}=e;if(range&&colors){const[rangeMin,rangeMax]=range;if(isNumberAlias(rangeMin)&&isNumberAlias(rangeMax)&&rangeMin<rangeMax){if(this.state.CI)this.state.CI.setColors(colors,rangeMin,rangeMax,.1);else this.updateProps({CI:new ColorInterpolator(colors,rangeMin,rangeMax,.1)})}}}clearImageData(){const{ctx,canvas:{height,width}}=this.state;if(height&&width)this.updateProps({imageData:ctx.createImageData(width,height)})}clearRect(){super.clearRect();this.clearImageData()}clear(){this.clearRect();this.updateProps({data:[]})}resize(){super.resize();this.clearImageData()}setRange(range){if(!range)return;this.updateProps({range});this.draw()}render(data){if(data?.length>=0){this.state.data=data;this.draw()}}draw(){const{imageData,data,canvas,ctx,CI}=this.state;if(0===data.length)return;fillImageData(canvas.width,canvas.height,data,imageData.data,value=>CI.getColor(value));ctx.putImageData(imageData,0,0)}}function createHeatmap(props){const{renderer=enums_RendererType.Canvas}=props;if(renderer===enums_RendererType.WebGL){if(isWebGL2Supported())return new HeatmapWebGL(props);console.warn("[Heatmap] WebGL 2.0 not supported, falling back to Canvas")}return new HeatmapCanvas(props)}const Heatmap_Heatmap=function(props){return createHeatmap(props)};const cartesian_Heatmap=Heatmap_Heatmap;const ENABLE_AREA_GRADIENT=false;class SeriesCanvas extends Engine{init(props){super.init(props);const{series,barValue2Color,disabledClearRect,colors}=props;this.updateProps({interval:0,series:{},barValue2Color,disabledClearRect,colors});this.setRange(this.state.range);if(Array.isArray(series))series.forEach(e=>{this.setSeries(e)})}updateProps(e){super.updateProps(e);const{colors=this.state.colors}=e;if(this.state.barValue2Color&&!this.state.CI&&colors)this.updateProps({CI:new ColorInterpolator(colors,0,100,.1)})}clear(){super.clearRect();const{series}=this.state;const seriesArray=Object.entries(series);seriesArray.forEach(([name,i])=>{this.setSeries({...i,name,data:void 0})})}setRange(range){this.updateProps({range:[range[0],range[1],range[1]-range[0]]});this.draw()}setIntervel(interval){if(!interval)return;this.updateProps({interval})}setSeries(e){if(e?.name){const{name}=e;const{series}=this.state;series[name]={thickness:DEFAULTS.THICKNESS,display:true,color:DEFAULTS.COLOR,type:enums_GraphicType.Line,data:void 0,path:void 0,orientation:enums_OrientationType.Vertical,...series[name],...e};this.draw()}}render(e){e&&this.setSeries(e);this.draw()}draw(){const{series,ctx,disabledClearRect}=this.state;if(!disabledClearRect)this.clearRect();const seriesArray=Object.values(series);const{range,canvas}=this.state;const min=range[0];const max=range[1];const{width,height}=canvas;const rangeY=max-min;ctx.save();ctx.translate(.5,.5);for(let s=0;s<seriesArray.length;s+=1){const seriesItem=seriesArray[s];const{display,type,data,orientation}=seriesItem;const thickness=seriesItem.thickness??DEFAULTS.THICKNESS;const color=seriesItem.color??DEFAULTS.COLOR;if(!data||!display)continue;const len=data.length;const per=width/len;switch(type){case enums_GraphicType.Line:case enums_GraphicType.Stepline:ctx.lineWidth=thickness;ctx.strokeStyle=color;break;case enums_GraphicType.Circle:case enums_GraphicType.Rect:case enums_GraphicType.Bar:case enums_GraphicType.Area:ctx.fillStyle=color;break}if(type===enums_GraphicType.Line){let points=[];for(let i=0;i<len;i+=1){const value=data[i];if(void 0===value||Number.isNaN(value)){if(points.length>0){ctx.beginPath();ctx.moveTo(points[0][0],points[0][1]);for(let j=1;j<points.length;j++)ctx.lineTo(points[j][0],points[j][1]);ctx.lineWidth=thickness;ctx.strokeStyle=color;ctx.stroke();points=[]}}else{const x=Math.round((i+.5)*per);const y=Math.round((value-min)/rangeY*height);points.push([x,y])}}if(points.length>0){ctx.beginPath();ctx.moveTo(points[0][0],points[0][1]);for(let j=1;j<points.length;j++)ctx.lineTo(points[j][0],points[j][1]);ctx.lineWidth=thickness;ctx.strokeStyle=color;ctx.stroke()}}if(type===enums_GraphicType.Stepline){let points=[];for(let i=0;i<len;i+=1){const value=data[i];if(void 0===value||Number.isNaN(value)){if(points.length>0){ctx.beginPath();ctx.moveTo(points[0][0],points[0][1]);for(let j=1;j<points.length;j++){const[_,py]=points[j-1];const[cx,cy]=points[j];ctx.lineTo(cx,py);ctx.lineTo(cx,cy)}const[lx,ly]=points[points.length-1];const endX=Math.round(lx+per);ctx.lineTo(endX,ly);ctx.lineWidth=thickness;ctx.strokeStyle=color;ctx.stroke();points=[]}}else{const x=Math.round(i*per);const y=Math.round((value-min)/rangeY*height);points.push([x,y])}}if(points.length>0){ctx.beginPath();ctx.moveTo(points[0][0],points[0][1]);for(let j=1;j<points.length;j++){const[_,py]=points[j-1];const[cx,cy]=points[j];ctx.lineTo(cx,py);ctx.lineTo(cx,cy)}const[lx,ly]=points[points.length-1];const endX=Math.round(lx+per);ctx.lineTo(endX,ly);ctx.lineWidth=thickness;ctx.strokeStyle=color;ctx.stroke()}}if(type===enums_GraphicType.Circle){const radius=+thickness/2;const endAngle=2*Math.PI;for(let i=0;i<len;i+=1){const x=Math.round((i+.5)*per);const y=Math.round((data[i]-min)/rangeY*height);ctx.beginPath();ctx.arc(x,y,radius,0,endAngle);ctx.fillStyle=color;ctx.fill()}}if(type===enums_GraphicType.Rect){const side=+thickness;for(let i=0;i<len;i+=1){const x=Math.round((i+.5)*per);const y=Math.round((data[i]-min)/rangeY*height);ctx.beginPath();ctx.rect(x,y,side,side);ctx.fillStyle=color;ctx.fill()}}if(type===enums_GraphicType.Area){let points=[];const{r,g,b}=hexToRGBA(color??DEFAULTS.COLOR);const drawArea=pointsToDraw=>{if(0===pointsToDraw.length)return;const[firstX,firstY]=pointsToDraw[0];const[lastX,lastY]=pointsToDraw[pointsToDraw.length-1];const endX=Math.round(lastX+per);ctx.beginPath();ctx.moveTo(firstX,0);ctx.lineTo(firstX,firstY);for(let j=1;j<pointsToDraw.length;j++){const[_,prevY]=pointsToDraw[j-1];const[currX,currY]=pointsToDraw[j];ctx.lineTo(currX,prevY);ctx.lineTo(currX,currY)}ctx.lineTo(endX,lastY);ctx.lineTo(endX,0);ctx.closePath();if(ENABLE_AREA_GRADIENT){const gradient=ctx.createLinearGradient(0,0,0,height);gradient.addColorStop(0,`rgba(${r}, ${g}, ${b}, 0)`);gradient.addColorStop(1,`rgba(${r}, ${g}, ${b}, 0.5)`);ctx.fillStyle=gradient}else ctx.fillStyle=`rgba(${r}, ${g}, ${b}, 1)`;ctx.fill()};for(let i=0;i<len;i+=1){const value=data[i];if(void 0===value||Number.isNaN(value)){drawArea(points);points=[]}else points.push([Math.round(i*per),Math.round((value-min)/rangeY*height)])}drawArea(points)}if(type===enums_GraphicType.Bar){if(orientation===enums_OrientationType.Horizontal){const h=height/len;const hh=Math.round(h);for(let i=0;i<len;i+=1){const y=Math.round(height-(i+1)*h);const w=Math.round((data[i]-min)/rangeY*width);const x=width-w;ctx.beginPath();ctx.rect(x,y,w,hh);ctx.fillStyle=color;ctx.fill()}}else for(let i=0;i<len;i+=1){const startX=Math.floor(i*width/len);const endX=Math.floor((i+1)*width/len);const w=endX-startX;const h=Math.round((data[i]-min)/rangeY*height);ctx.beginPath();ctx.rect(startX,0,w,h);ctx.fillStyle=this.state.barValue2Color?this.state.CI?.getColor(data[i]).hax??color:color;ctx.fill()}}}ctx.restore()}}function createSeries(props){return new SeriesCanvas(props)}const Series_Series=function(props){return createSeries(props)};const cartesian_Series=Series_Series;function getCoordinates(radius,padding,angle){const x=radius+(radius-padding)*Math.cos(angle);const y=radius+(radius-padding)*Math.sin(angle);return[x,y]}class CircularBase extends Engine{getSpecialTickConfig(_angle){}getAngleOffset(){return 0}init(props){super.init(props);this.state.canvas.style.transform="scaleY(1)";const fillStyle=props.fillStyle??DEFAULTS.FILL_STYLE;const fillStylePrimary=props.fillStylePrimary??DEFAULTS.FILL_STYLE_PRIMARY;const fillStyleTransparentBase=props.fillStyleTransparentBase??DEFAULTS.FILL_STYLE_TRANSPARENT_BASE;const markedNum=12;const baseWidth=6;this.updateProps({fillStyle,fillStylePrimary,fillStyleTransparentBase,baseWidth,padding:10*baseWidth,ticksStep:6,ticksRenderLength:5,markedAngles:Array.from({length:markedNum},(_,i)=>360/markedNum*i),color2intensity:color2intensity(fillStylePrimary),data:{series:[],range:[0,0],polygon:[],yawAngle:0,isNorthFacing:true}})}clear(){this.clearRect()}resize(){super.resize();if(this.state.markedAngles)this.drawBackground();if(this.state.data)this.drawTicks()}render(data){const prevData=this.state.data;const nextData={...prevData,...data};const shouldRedrawTicks=void 0!==data.yawAngle&&data.yawAngle!==prevData?.yawAngle||void 0!==data.isNorthFacing&&data.isNorthFacing!==prevData?.isNorthFacing;this.state.data=nextData;if(shouldRedrawTicks)this.drawTicks();this.draw()}drawTicks(){const{canvas,baseWidth,fillStyle,fillStylePrimary,ticksStep,ticksRenderLength,markedAngles,data}=this.state;const{yawAngle=0,isNorthFacing=true}=data;const ticksCanvas=document.createElement("canvas");ticksCanvas.width=canvas.width;ticksCanvas.height=canvas.height;const ticksCtx=ticksCanvas.getContext("2d");if(!ticksCtx)return;const radius=canvas.width/2;const northYawgle=isNorthFacing?0:-yawAngle;for(let i=0;i<360;i+=ticksStep){const angle=(i+northYawgle)*Math.PI/180;const isMarked=markedAngles.includes(i);const length=ticksRenderLength*(isMarked?1.2:1);const[x,y]=getCoordinates(radius,3.5*baseWidth+length+(isMarked?3:0),angle);const[xOuter,yOuter]=getCoordinates(radius,3.5*baseWidth,angle);ticksCtx.beginPath();ticksCtx.moveTo(x,y);ticksCtx.lineTo(xOuter,yOuter);ticksCtx.strokeStyle=fillStyle;ticksCtx.lineWidth=isMarked?2:.5;ticksCtx.stroke()}ticksCtx.font=`${2*baseWidth}px Arial`;ticksCtx.textAlign="center";ticksCtx.textBaseline="middle";markedAngles.forEach(angle=>{const radian=(angle+northYawgle-90)*Math.PI/180;const[x,y]=getCoordinates(radius,8*baseWidth,radian);const specialConfig=this.getSpecialTickConfig(angle);if(specialConfig){ticksCtx.fillStyle=specialConfig.color;ticksCtx.fillText(specialConfig.alias,x,y)}else{ticksCtx.fillStyle=fillStyle;ticksCtx.fillText(angle.toString(),x,y)}});const arrowWidth=canvas.width/18;ticksCtx.translate(canvas.width/2,canvas.height/2);ticksCtx.scale(1,-1);ticksCtx.rotate((isNorthFacing?-yawAngle:0)*Math.PI/180);ticksCtx.lineJoin="round";ticksCtx.lineCap="round";ticksCtx.strokeStyle=fillStylePrimary;ticksCtx.fillStyle=fillStylePrimary;ticksCtx.lineWidth=arrowWidth/6;ticksCtx.beginPath();ticksCtx.moveTo(0,arrowWidth/2);ticksCtx.lineTo(arrowWidth/2,-arrowWidth/2);ticksCtx.lineTo(0,arrowWidth/2-1/((1+Math.sqrt(5))/2)*arrowWidth);ticksCtx.lineTo(-arrowWidth/2,-arrowWidth/2);ticksCtx.lineTo(0,arrowWidth/2);ticksCtx.fill();ticksCtx.stroke();this.state.ticksCanvas=ticksCanvas}drawBackground(){const{canvas,baseWidth,padding,fillStyleTransparentBase}=this.state;const BGCanvas=document.createElement("canvas");BGCanvas.width=canvas.width;BGCanvas.height=canvas.height;const BGCtx=BGCanvas.getContext("2d");if(!BGCtx)return;const radius=canvas.width/2;BGCtx.beginPath();BGCtx.arc(radius,radius,radius-2*baseWidth,0,2*Math.PI);BGCtx.strokeStyle=fillStyleTransparentBase;BGCtx.lineWidth=baseWidth;BGCtx.stroke();new Array(5).fill(1).forEach((_,i)=>{BGCtx.beginPath();BGCtx.arc(radius,radius,(radius-padding)*(i+1)/5,0,2*Math.PI);BGCtx.strokeStyle=fillStyleTransparentBase;BGCtx.lineWidth=baseWidth/4;BGCtx.stroke()});this.state.BGCanvas=BGCanvas}draw(){const{data,canvas,ctx,baseWidth,padding,BGCanvas,ticksCanvas,color2intensity:c2i,fillStylePrimary,fillStyleTransparentBase}=this.state;const{series,range,polygon,isNorthFacing=true,yawAngle=0}=data;if(!series&&!polygon)return;this.clearRect();const radius=canvas.width/2;const northYawgle=isNorthFacing?0:-yawAngle;const angleOffset=this.getAngleOffset();if(BGCanvas)ctx.drawImage(BGCanvas,0,0);if(range){const sliceAngle=360/range.length;range.forEach((value,index)=>{const startAngle=(index*sliceAngle+northYawgle+angleOffset)*Math.PI/180;const endAngle=((index+1)*sliceAngle+northYawgle+angleOffset)*Math.PI/180;ctx.beginPath();ctx.moveTo(radius,radius);ctx.arc(radius,radius,radius-padding,startAngle,endAngle);ctx.closePath();ctx.fillStyle=c2i[value];ctx.fill()})}if(polygon?.length)for(const poly of polygon){const{values,color}=poly;if(!values||values.length<3)continue;const sliceAngle=360/values.length;const polygonColor=color??fillStylePrimary;ctx.beginPath();values.forEach((value,index)=>{const angle=(index*sliceAngle+northYawgle+angleOffset)*Math.PI/180;const radial=Math.max(0,Math.min(1,value))*(radius-padding);const[x,y]=getCoordinates(radius,radius-radial,angle);if(0===index)ctx.moveTo(x,y);else ctx.lineTo(x,y)});ctx.closePath();ctx.save();ctx.globalAlpha=.3;ctx.fillStyle=polygonColor;ctx.fill();ctx.globalAlpha=1;ctx.strokeStyle=polygonColor;ctx.lineWidth=baseWidth;ctx.lineJoin="round";ctx.lineCap="round";ctx.stroke();ctx.restore()}series?.forEach(({value,color,lineWidth=baseWidth,radio=1})=>{if(null==value)return;const pointerAngle=(value+northYawgle)*Math.PI/180-Math.PI/2;const[startX,startY]=getCoordinates(radius,radius,pointerAngle);const[endX,endY]=getCoordinates(radius,padding+(1-radio)*(radius-padding)+lineWidth/2,pointerAngle);if(1!==radio){ctx.beginPath();ctx.moveTo(...getCoordinates(radius,padding+lineWidth/2,pointerAngle));ctx.lineTo(endX,endY);ctx.strokeStyle=fillStyleTransparentBase;ctx.stroke()}ctx.beginPath();ctx.moveTo(endX,endY);ctx.lineTo(startX,startY);ctx.strokeStyle=color;ctx.lineWidth=lineWidth;ctx.lineJoin="round";ctx.lineCap="round";ctx.stroke()});if(ticksCanvas)ctx.drawImage(ticksCanvas,0,0)}}const SPECIAL_TICK_CONFIG={0:{color:DEFAULTS.DIAL_NORTH_COLOR,alias:"北"},180:{color:DEFAULTS.DIAL_SOUTH_COLOR,alias:"南"}};class Dial extends CircularBase{getSpecialTickConfig(angle){return SPECIAL_TICK_CONFIG[angle]}getAngleOffset(){return -90}}class Radar extends CircularBase{getSpecialTickConfig(_angle){}getAngleOffset(){return 0}}class IQ extends Engine{init(props){super.init(props);this.updateProps({lineColor:props.lineColor??DEFAULTS.LINE_COLOR,pointColor:props.pointColor??DEFAULTS.POINT_COLOR})}clear(){this.clearRect();this.updateProps({data:{IData:[],QData:[]}})}render(data){if(data.IData&&data.QData){this.state.data=data;this.draw()}}draw(){const{data,canvas,pointColor,lineColor,ctx}=this.state;if(!data)return;const{IData,QData}=data;if(0===IData.length||0===QData.length)return;const{width,height}=ctx.canvas;ctx.clearRect(0,0,canvas.width,canvas.height);const[minX,maxX]=getMinMax(IData);const[minY,maxY]=getMinMax([minX,maxX,...QData]);const xScale=width/(maxX-minX);const yScale=height/(maxY-minY);const pointRadius=2;const points=IData.map((xVal,i)=>{const x=(xVal-minX)*xScale;const y=(QData[i]-minY)*yScale;return{x,y}});ctx.beginPath();points.forEach((point,i)=>{ctx[0===i?"moveTo":"lineTo"](point.x,point.y)});ctx.strokeStyle=lineColor;ctx.lineWidth=1;ctx.stroke();points.forEach(point=>{ctx.beginPath();ctx.arc(point.x,point.y,pointRadius,0,2*Math.PI);ctx.fillStyle=pointColor;ctx.fill()})}}const PIXEL_STRIDE=4;const COLOR_LOOKUP_STEPS=256;const createColorLookup=colors=>{const lookup=[];for(let i=0;i<COLOR_LOOKUP_STEPS;i++){const t=i/(COLOR_LOOKUP_STEPS-1);const segmentCount=colors.length-1;const segment=Math.min(Math.floor(t*segmentCount),segmentCount-1);const localT=t*segmentCount-segment;const c1=parseColor(colors[segment]);const c2=parseColor(colors[segment+1]);lookup.push({r:Math.round(c1.r+(c2.r-c1.r)*localT),g:Math.round(c1.g+(c2.g-c1.g)*localT),b:Math.round(c1.b+(c2.b-c1.b)*localT)})}return lookup};const createClassicPixelLookup=()=>{const colorLookup=createColorLookup(EYE_DEFAULTS.CLASSIC_COLORS);const lookup=new Uint8ClampedArray((EYE_DEFAULTS.MAX_ACCUMULATION+1)*PIXEL_STRIDE);const maxColorIndex=colorLookup.length-1;for(let acc=1;acc<=EYE_DEFAULTS.MAX_ACCUMULATION;acc++){const intensity=Math.min(acc*EYE_DEFAULTS.CLASSIC_ALPHA_GAIN/255,1);const colorIdx=Math.min(Math.floor(intensity*maxColorIndex),maxColorIndex);const color=colorLookup[colorIdx];const idx=acc*PIXEL_STRIDE;lookup[idx]=color.r;lookup[idx+1]=color.g;lookup[idx+2]=color.b;lookup[idx+3]=Math.min(acc*EYE_DEFAULTS.CLASSIC_ALPHA_GAIN,255)}return lookup};const CLASSIC_PIXEL_LOOKUP=createClassicPixelLookup();class IQEye extends Engine{init(props){super.init(props);this.state.iqEyeMode=this.resolveMode(props.iqEyeMode);this.initColorLookup()}resize(){super.resize();const{width,height}=this.state.canvas;this.state.accumulator=new Uint16Array(width*height);this.state.imageData=this.state.ctx.createImageData(width,height)}clear(){this.clearRect();this.state.accumulator?.fill(0);this.updateProps({data:{IData:[],QData:[]}})}render(data){if(!data.IData?.length||!data.QData?.length)return;this.state.data=data;if(!this.state.accumulator)return;if("classic"===this.state.iqEyeMode)this.state.accumulator.fill(0);else this.applyDecay();this.accumulateData(data);this.draw()}draw(){if("classic"===this.state.iqEyeMode){this.drawClassic();return}this.drawModern()}drawClassic(){const{accumulator,imageData,ctx}=this.state;if(!accumulator||!imageData)return;const pixelData=imageData.data;for(let i=0;i<accumulator.length;i++){const acc=accumulator[i];const idx=i*PIXEL_STRIDE;if(acc>0){const lookupIndex=acc*PIXEL_STRIDE;pixelData[idx]=CLASSIC_PIXEL_LOOKUP[lookupIndex];pixelData[idx+1]=CLASSIC_PIXEL_LOOKUP[lookupIndex+1];pixelData[idx+2]=CLASSIC_PIXEL_LOOKUP[lookupIndex+2];pixelData[idx+3]=CLASSIC_PIXEL_LOOKUP[lookupIndex+3]}else pixelData[idx+3]=0}ctx.putImageData(imageData,0,0)}drawModern(){const{accumulator,imageData,ctx,colorLookup}=this.state;if(!accumulator||!imageData||!colorLookup)return;const pixelData=imageData.data;const maxAcc=EYE_DEFAULTS.MAX_ACCUMULATION;for(let i=0;i<accumulator.length;i++){const acc=accumulator[i];const idx=i*PIXEL_STRIDE;if(acc>0){const intensity=Math.min(acc/maxAcc,1);const colorIdx=Math.min(Math.floor(intensity*(colorLookup.length-1)),colorLookup.length-1);const color=colorLookup[colorIdx];pixelData[idx]=color.r;pixelData[idx+1]=color.g;pixelData[idx+2]=color.b;pixelData[idx+3]=Math.round(255*Math.min(2*intensity,1))}else pixelData[idx+3]=0}ctx.putImageData(imageData,0,0)}initColorLookup(){const colors="classic"===this.state.iqEyeMode?EYE_DEFAULTS.CLASSIC_COLORS:EYE_DEFAULTS.MODERN_COLORS;this.state.colorLookup=createColorLookup(colors)}resolveMode(mode){if("modern"===mode)return"modern";return EYE_DEFAULTS.MODE}applyDecay(){const{accumulator}=this.state;if(!accumulator)return;const decay=EYE_DEFAULTS.DECAY_FACTOR;for(let i=0;i<accumulator.length;i++)accumulator[i]=Math.floor(accumulator[i]*decay)}accumulateData(data){const{accumulator,canvas}=this.state;if(!accumulator)return;const{width,height}=canvas;const[yMin,yMax]=EYE_DEFAULTS.Y_RANGE;const rangeY=yMax-yMin;const segmentSize=EYE_DEFAULTS.SEGMENT_SIZE;const pointSpacing=width/(segmentSize-1);this.accumulateSegments(data.IData,yMin,rangeY,width,height,segmentSize,pointSpacing,accumulator);this.accumulateSegments(data.QData,yMin,rangeY,width,height,segmentSize,pointSpacing,accumulator)}accumulateSegments(data,yMin,rangeY,width,height,segmentSize,pointSpacing,accumulator){const segmentCount=Math.ceil(data.length/segmentSize);for(let seg=0;seg<segmentCount;seg++){let prevX=-1,prevY=-1;for(let i=0;i<segmentSize;i++){const idx=seg*segmentSize+i;if(idx>=data.length)break;const value=data[idx];if(void 0===value)continue;const x=Math.round(i*pointSpacing);const y=Math.round((value-yMin)/rangeY*(height-1));if(prevX>=0)this.drawLine(prevX,prevY,x,y,width,height,accumulator);prevX=x;prevY=y}}}drawLine(x0,y0,x1,y1,width,height,accumulator){const dx=Math.abs(x1-x0);const dy=Math.abs(y1-y0);const sx=x0<x1?1:-1;const sy=y0<y1?1:-1;let x=x0,y=y0,err=dx-dy;const maxAcc=EYE_DEFAULTS.MAX_ACCUMULATION;while(true){if(x>=0&&x<width&&y>=0&&y<height){const idx=y*width+x;if(accumulator[idx]<maxAcc)accumulator[idx]++}if(x===x1&&y===y1)break;const e2=2*err;if(e2>-dy){err-=dy;x+=sx}if(e2<dx){err+=dx;y+=sy}}}}export{ColorInterpolator,Dial,Fluorescence,enums_FluorescenceRenderMode as FluorescenceRenderMode,Gauge,enums_GraphicType as GraphicType,cartesian_Heatmap as Heatmap,HeatmapCanvas,HeatmapWebGL,IQ,IQEye,enums_OrientationType as OrientationType,Radar,enums_RendererType as RendererType,cartesian_Series as Series,SeriesCanvas,color2intensity,createHeatmap,createSeries,hexToRGBA,rgbToHex};
|
|
46
|
+
`;class HeatmapWebGL extends WebGLEngine{init(props){super.init(props);const{gl}=this.state;if(!gl)return;const colorsInput=props.colors;const colors=colorsInput&&colorsInput.length>0?colorsInput:DEFAULTS.FLUORESCENCE_COLORS;const program=this.createProgram({vertex:VERTEX_SHADER,fragment:FRAGMENT_SHADER});const quadBuffer=this.createQuadBuffer();const dataTexture=this.createTexture();const colorTexture=this.createTexture();const maxTextureSize=gl.getParameter(gl.MAX_TEXTURE_SIZE);if(dataTexture){gl.activeTexture(gl.TEXTURE0);gl.bindTexture(gl.TEXTURE_2D,dataTexture);const emptyData=new Float32Array([INVALID_VALUE]);gl.texImage2D(gl.TEXTURE_2D,0,gl.R32F,1,1,0,gl.RED,gl.FLOAT,emptyData)}const range=props.range??DEFAULTS.RANGE;const[rangeMin,rangeMax]=range;const CI=new ColorInterpolator(colors,rangeMin,rangeMax,.1);this.updateProps({data:[],colors,program,quadBuffer,dataTexture,dataTextures:dataTexture?[dataTexture]:[],dataTiles:[],colorTexture,dataWidth:0,dataHeight:0,CI,maxTextureSize});gl.enable(gl.BLEND);gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);this.updateColorTexture();gl.clearColor(0,0,0,0);gl.clear(gl.COLOR_BUFFER_BIT);this.resize()}updateColorTexture(){const{gl,colorTexture,CI,range}=this.state;if(!gl||!colorTexture||!CI)return;const[rangeMin,rangeMax]=range;const size=256;const colorData=new Uint8Array(4*size);for(let i=0;i<size;i++){const value=rangeMin+i/(size-1)*(rangeMax-rangeMin);const color=CI.getColor(value);colorData[4*i]=color.r;colorData[4*i+1]=color.g;colorData[4*i+2]=color.b;colorData[4*i+3]=color.a}gl.activeTexture(gl.TEXTURE1);gl.bindTexture(gl.TEXTURE_2D,colorTexture);gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,size,1,0,gl.RGBA,gl.UNSIGNED_BYTE,colorData)}updateDataTexture(){const{gl,data}=this.state;if(!gl||0===data.length)return;const height=data.length;let width=0;for(let row=0;row<height;row++){const rowLen=data[row]?.length??0;if(rowLen>width)width=rowLen}if(0===width)return;const maxSize=this.state.maxTextureSize||gl.getParameter(gl.MAX_TEXTURE_SIZE);if(!maxSize||maxSize<=0)return;const tilesX=Math.ceil(width/maxSize);const tilesY=Math.ceil(height/maxSize);const nextTileCount=tilesX*tilesY;let dataTextures=this.state.dataTextures??[];if(dataTextures.length<nextTileCount){const missing=nextTileCount-dataTextures.length;for(let i=0;i<missing;i++){const tex=this.createTexture();if(tex)dataTextures.push(tex)}}else if(dataTextures.length>nextTileCount){for(let i=nextTileCount;i<dataTextures.length;i++)gl.deleteTexture(dataTextures[i]);dataTextures=dataTextures.slice(0,nextTileCount)}const dataTiles=[];gl.activeTexture(gl.TEXTURE0);for(let ty=0;ty<tilesY;ty++){const startRow=ty*maxSize;const tileH=Math.min(maxSize,height-startRow);for(let tx=0;tx<tilesX;tx++){const startCol=tx*maxSize;const tileW=Math.min(maxSize,width-startCol);const tileIndex=ty*tilesX+tx;const texture=dataTextures[tileIndex];if(!texture)continue;const floatData=new Float32Array(tileW*tileH);for(let localRow=0;localRow<tileH;localRow++){const globalRow=startRow+localRow;const rowData=data[globalRow];const rowLen=rowData?.length??0;const rowOffset=localRow*tileW;for(let localCol=0;localCol<tileW;localCol++){const globalCol=startCol+localCol;if(0===rowLen||globalCol>=rowLen){floatData[rowOffset+localCol]=INVALID_VALUE;continue}const value=rowData[globalCol];if(void 0===value||Number.isNaN(value))floatData[rowOffset+localCol]=INVALID_VALUE;else floatData[rowOffset+localCol]=value}}gl.bindTexture(gl.TEXTURE_2D,texture);gl.texImage2D(gl.TEXTURE_2D,0,gl.R32F,tileW,tileH,0,gl.RED,gl.FLOAT,floatData);dataTiles[tileIndex]={startCol,startRow,width:tileW,height:tileH}}}this.updateProps({dataTexture:dataTextures[0]??null,dataTextures,dataTiles,dataWidth:width,dataHeight:height})}updateProps(e){super.updateProps(e);const colors=e.colors??this.state.colors;const range=e.range??this.state.range;if((e.colors||e.range)&&colors.length>0){const[rangeMin,rangeMax]=range;if(this.state.CI)this.state.CI.setColors(colors,rangeMin,rangeMax,.1);else this.state.CI=new ColorInterpolator(colors,rangeMin,rangeMax,.1);this.updateColorTexture()}}setRange(range){if(!range)return;this.updateProps({range});this.draw()}clear(){this.clearRect();this.updateProps({data:[]})}render(data){if(data?.length>0){this.state.data=data;this.updateDataTexture();this.draw()}}draw(){const{gl,program,quadBuffer,dataTextures,dataTiles,colorTexture,data,range,dataWidth,dataHeight,canvas}=this.state;if(!gl||!program||!quadBuffer||!colorTexture)return;gl.clearColor(0,0,0,0);gl.clear(gl.COLOR_BUFFER_BIT);if(0===data.length||0===dataWidth||0===dataHeight||0===dataTiles.length||0===dataTextures.length)return;const[rangeMin,rangeMax]=range;gl.useProgram(program);gl.bindBuffer(gl.ARRAY_BUFFER,quadBuffer);const positionLoc=gl.getAttribLocation(program,"a_position");gl.enableVertexAttribArray(positionLoc);gl.vertexAttribPointer(positionLoc,2,gl.FLOAT,false,0,0);const dataTextureLoc=gl.getUniformLocation(program,"u_dataTexture");const colorTextureLoc=gl.getUniformLocation(program,"u_colorTexture");const rangeMinLoc=gl.getUniformLocation(program,"u_rangeMin");const rangeMaxLoc=gl.getUniformLocation(program,"u_rangeMax");if(!dataTextureLoc||!colorTextureLoc||!rangeMinLoc||!rangeMaxLoc)return;gl.activeTexture(gl.TEXTURE1);gl.bindTexture(gl.TEXTURE_2D,colorTexture);gl.uniform1i(colorTextureLoc,1);gl.uniform1f(rangeMinLoc,rangeMin);gl.uniform1f(rangeMaxLoc,rangeMax);const canvasWidth=canvas.width;const canvasHeight=canvas.height;const toPixX=col=>Math.round(col/dataWidth*canvasWidth);const toPixYFromTop=row=>Math.round(row/dataHeight*canvasHeight);for(let i=0;i<dataTiles.length;i++){const tile=dataTiles[i];const texture=dataTextures[i];if(!tile||!texture)continue;const x0=toPixX(tile.startCol);const x1=toPixX(tile.startCol+tile.width);const yTop0=toPixYFromTop(tile.startRow);const yTop1=toPixYFromTop(tile.startRow+tile.height);const y0=canvasHeight-yTop1;const y1=canvasHeight-yTop0;const w=x1-x0;const h=y1-y0;if(!(w<=0)&&!(h<=0)){gl.viewport(x0,y0,w,h);gl.activeTexture(gl.TEXTURE0);gl.bindTexture(gl.TEXTURE_2D,texture);gl.uniform1i(dataTextureLoc,0);gl.drawArrays(gl.TRIANGLES,0,6)}}gl.viewport(0,0,canvasWidth,canvasHeight)}resize(){super.resize()}dispose(){const{gl,program,quadBuffer,dataTextures,colorTexture}=this.state;if(gl){if(program)gl.deleteProgram(program);if(quadBuffer)gl.deleteBuffer(quadBuffer);for(const tex of dataTextures)gl.deleteTexture(tex);if(colorTexture)gl.deleteTexture(colorTexture)}super.dispose()}}class HeatmapCanvas extends Engine{init(props){super.init(props);const{colors}=props;this.updateProps({colors,data:[]});this.resize()}updateProps(e){super.updateProps(e);const{range=this.state.range,colors=this.state.colors}=e;if(range&&colors){const[rangeMin,rangeMax]=range;if(isNumberAlias(rangeMin)&&isNumberAlias(rangeMax)&&rangeMin<rangeMax){if(this.state.CI)this.state.CI.setColors(colors,rangeMin,rangeMax,.1);else this.updateProps({CI:new ColorInterpolator(colors,rangeMin,rangeMax,.1)})}}}clearImageData(){const{ctx,canvas:{height,width}}=this.state;if(height&&width)this.updateProps({imageData:ctx.createImageData(width,height)})}clearRect(){super.clearRect();this.clearImageData()}clear(){this.clearRect();this.updateProps({data:[]})}resize(){super.resize();this.clearImageData()}setRange(range){if(!range)return;this.updateProps({range});this.draw()}render(data){if(data?.length>=0){this.state.data=data;this.draw()}}draw(){const{imageData,data,canvas,ctx,CI}=this.state;if(0===data.length)return;fillImageData(canvas.width,canvas.height,data,imageData.data,value=>CI.getColor(value));ctx.putImageData(imageData,0,0)}}function createHeatmap(props){const{renderer=enums_RendererType.Canvas}=props;if(renderer===enums_RendererType.WebGL){if(isWebGL2Supported())return new HeatmapWebGL(props);console.warn("[Heatmap] WebGL 2.0 not supported, falling back to Canvas")}return new HeatmapCanvas(props)}const Heatmap_Heatmap=function(props){return createHeatmap(props)};const cartesian_Heatmap=Heatmap_Heatmap;const ENABLE_AREA_GRADIENT=false;class SeriesCanvas extends Engine{init(props){super.init(props);const{series,barValue2Color,disabledClearRect,colors}=props;this.updateProps({interval:0,series:{},barValue2Color,disabledClearRect,colors});this.setRange(this.state.range);if(Array.isArray(series))series.forEach(e=>{this.setSeries(e)})}updateProps(e){super.updateProps(e);const{colors=this.state.colors}=e;if(this.state.barValue2Color&&!this.state.CI&&colors)this.updateProps({CI:new ColorInterpolator(colors,0,100,.1)})}clear(){super.clearRect();const{series}=this.state;const seriesArray=Object.entries(series);seriesArray.forEach(([name,i])=>{this.setSeries({...i,name,data:void 0})})}setRange(range){this.updateProps({range:[range[0],range[1],range[1]-range[0]]});this.draw()}setIntervel(interval){if(!interval)return;this.updateProps({interval})}setSeries(e){if(e?.name){const{name}=e;const{series}=this.state;series[name]={thickness:DEFAULTS.THICKNESS,display:true,color:DEFAULTS.COLOR,type:enums_GraphicType.Line,data:void 0,path:void 0,orientation:enums_OrientationType.Vertical,...series[name],...e};this.draw()}}render(e){e&&this.setSeries(e);this.draw()}draw(){const{series,ctx,disabledClearRect}=this.state;if(!disabledClearRect)this.clearRect();const seriesArray=Object.values(series);const{range,canvas}=this.state;const min=range[0];const max=range[1];const{width,height}=canvas;const rangeY=max-min;ctx.save();ctx.translate(.5,.5);for(let s=0;s<seriesArray.length;s+=1){const seriesItem=seriesArray[s];const{display,type,data,orientation}=seriesItem;const thickness=seriesItem.thickness??DEFAULTS.THICKNESS;const color=seriesItem.color??DEFAULTS.COLOR;if(!data||!display)continue;const len=data.length;const per=width/len;switch(type){case enums_GraphicType.Line:case enums_GraphicType.Stepline:ctx.lineWidth=thickness;ctx.strokeStyle=color;break;case enums_GraphicType.Circle:case enums_GraphicType.Rect:case enums_GraphicType.Bar:case enums_GraphicType.Area:ctx.fillStyle=color;break}if(type===enums_GraphicType.Line){let points=[];for(let i=0;i<len;i+=1){const value=data[i];if(void 0===value||Number.isNaN(value)){if(points.length>0){ctx.beginPath();ctx.moveTo(points[0][0],points[0][1]);for(let j=1;j<points.length;j++)ctx.lineTo(points[j][0],points[j][1]);ctx.lineWidth=thickness;ctx.strokeStyle=color;ctx.stroke();points=[]}}else{const x=Math.round((i+.5)*per);const y=Math.round((value-min)/rangeY*height);points.push([x,y])}}if(points.length>0){ctx.beginPath();ctx.moveTo(points[0][0],points[0][1]);for(let j=1;j<points.length;j++)ctx.lineTo(points[j][0],points[j][1]);ctx.lineWidth=thickness;ctx.strokeStyle=color;ctx.stroke()}}if(type===enums_GraphicType.Stepline){let points=[];for(let i=0;i<len;i+=1){const value=data[i];if(void 0===value||Number.isNaN(value)){if(points.length>0){ctx.beginPath();ctx.moveTo(points[0][0],points[0][1]);for(let j=1;j<points.length;j++){const[_,py]=points[j-1];const[cx,cy]=points[j];ctx.lineTo(cx,py);ctx.lineTo(cx,cy)}const[lx,ly]=points[points.length-1];const endX=Math.round(lx+per);ctx.lineTo(endX,ly);ctx.lineWidth=thickness;ctx.strokeStyle=color;ctx.stroke();points=[]}}else{const x=Math.round(i*per);const y=Math.round((value-min)/rangeY*height);points.push([x,y])}}if(points.length>0){ctx.beginPath();ctx.moveTo(points[0][0],points[0][1]);for(let j=1;j<points.length;j++){const[_,py]=points[j-1];const[cx,cy]=points[j];ctx.lineTo(cx,py);ctx.lineTo(cx,cy)}const[lx,ly]=points[points.length-1];const endX=Math.round(lx+per);ctx.lineTo(endX,ly);ctx.lineWidth=thickness;ctx.strokeStyle=color;ctx.stroke()}}if(type===enums_GraphicType.Circle){const radius=+thickness/2;const endAngle=2*Math.PI;for(let i=0;i<len;i+=1){const x=Math.round((i+.5)*per);const y=Math.round((data[i]-min)/rangeY*height);ctx.beginPath();ctx.arc(x,y,radius,0,endAngle);ctx.fillStyle=color;ctx.fill()}}if(type===enums_GraphicType.Rect){const side=+thickness;for(let i=0;i<len;i+=1){const x=Math.round((i+.5)*per);const y=Math.round((data[i]-min)/rangeY*height);ctx.beginPath();ctx.rect(x,y,side,side);ctx.fillStyle=color;ctx.fill()}}if(type===enums_GraphicType.Area){let points=[];const{r,g,b}=hexToRGBA(color??DEFAULTS.COLOR);const drawArea=pointsToDraw=>{if(0===pointsToDraw.length)return;const[firstX,firstY]=pointsToDraw[0];const[lastX,lastY]=pointsToDraw[pointsToDraw.length-1];const endX=Math.round(lastX+per);ctx.beginPath();ctx.moveTo(firstX,0);ctx.lineTo(firstX,firstY);for(let j=1;j<pointsToDraw.length;j++){const[_,prevY]=pointsToDraw[j-1];const[currX,currY]=pointsToDraw[j];ctx.lineTo(currX,prevY);ctx.lineTo(currX,currY)}ctx.lineTo(endX,lastY);ctx.lineTo(endX,0);ctx.closePath();if(ENABLE_AREA_GRADIENT){const gradient=ctx.createLinearGradient(0,0,0,height);gradient.addColorStop(0,`rgba(${r}, ${g}, ${b}, 0)`);gradient.addColorStop(1,`rgba(${r}, ${g}, ${b}, 0.5)`);ctx.fillStyle=gradient}else ctx.fillStyle=`rgba(${r}, ${g}, ${b}, 1)`;ctx.fill()};for(let i=0;i<len;i+=1){const value=data[i];if(void 0===value||Number.isNaN(value)){drawArea(points);points=[]}else points.push([Math.round(i*per),Math.round((value-min)/rangeY*height)])}drawArea(points)}if(type===enums_GraphicType.Bar){if(orientation===enums_OrientationType.Horizontal){const h=height/len;const hh=Math.round(h);for(let i=0;i<len;i+=1){const y=Math.round(height-(i+1)*h);const w=Math.round((data[i]-min)/rangeY*width);const x=width-w;ctx.beginPath();ctx.rect(x,y,w,hh);ctx.fillStyle=color;ctx.fill()}}else for(let i=0;i<len;i+=1){const startX=Math.floor(i*width/len);const endX=Math.floor((i+1)*width/len);const w=endX-startX;const h=Math.round((data[i]-min)/rangeY*height);ctx.beginPath();ctx.rect(startX,0,w,h);ctx.fillStyle=this.state.barValue2Color?this.state.CI?.getColor(data[i]).hax??color:color;ctx.fill()}}}ctx.restore()}}function createSeries(props){return new SeriesCanvas(props)}const Series_Series=function(props){return createSeries(props)};const cartesian_Series=Series_Series;function getCoordinates(radius,padding,angle){const x=radius+(radius-padding)*Math.cos(angle);const y=radius+(radius-padding)*Math.sin(angle);return[x,y]}function getMaxPolygonValue(poly){let max=0;for(const value of poly.values)if(value>max)max=value;return max}class CircularBase extends Engine{getSpecialTickConfig(_angle){}getAngleOffset(){return 0}init(props){super.init(props);this.state.canvas.style.transform="scaleY(1)";const fillStyle=props.fillStyle??DEFAULTS.FILL_STYLE;const fillStylePrimary=props.fillStylePrimary??DEFAULTS.FILL_STYLE_PRIMARY;const fillStyleTransparentBase=props.fillStyleTransparentBase??DEFAULTS.FILL_STYLE_TRANSPARENT_BASE;const markedNum=12;const baseWidth=6;this.updateProps({fillStyle,fillStylePrimary,fillStyleTransparentBase,baseWidth,padding:10*baseWidth,ticksStep:6,ticksRenderLength:5,markedAngles:Array.from({length:markedNum},(_,i)=>360/markedNum*i),color2intensity:color2intensity(fillStylePrimary),data:{series:[],range:[0,0],polygon:[],yawAngle:0,isNorthFacing:true}})}clear(){this.clearRect()}resize(){super.resize();if(this.state.markedAngles)this.drawBackground();if(this.state.data)this.drawTicks()}render(data){const prevData=this.state.data;const nextData={...prevData,...data};const shouldRedrawTicks=void 0!==data.yawAngle&&data.yawAngle!==prevData?.yawAngle||void 0!==data.isNorthFacing&&data.isNorthFacing!==prevData?.isNorthFacing;this.state.data=nextData;if(shouldRedrawTicks)this.drawTicks();this.draw()}drawTicks(){const{canvas,baseWidth,fillStyle,fillStylePrimary,ticksStep,ticksRenderLength,markedAngles,data}=this.state;const{yawAngle=0,isNorthFacing=true}=data;const ticksCanvas=document.createElement("canvas");ticksCanvas.width=canvas.width;ticksCanvas.height=canvas.height;const ticksCtx=ticksCanvas.getContext("2d");if(!ticksCtx)return;const radius=canvas.width/2;const northYawgle=isNorthFacing?0:-yawAngle;for(let i=0;i<360;i+=ticksStep){const angle=(i+northYawgle)*Math.PI/180;const isMarked=markedAngles.includes(i);const length=ticksRenderLength*(isMarked?1.2:1);const[x,y]=getCoordinates(radius,3.5*baseWidth+length+(isMarked?3:0),angle);const[xOuter,yOuter]=getCoordinates(radius,3.5*baseWidth,angle);ticksCtx.beginPath();ticksCtx.moveTo(x,y);ticksCtx.lineTo(xOuter,yOuter);ticksCtx.strokeStyle=fillStyle;ticksCtx.lineWidth=isMarked?2:.5;ticksCtx.stroke()}ticksCtx.font=`${2*baseWidth}px Arial`;ticksCtx.textAlign="center";ticksCtx.textBaseline="middle";markedAngles.forEach(angle=>{const radian=(angle+northYawgle-90)*Math.PI/180;const[x,y]=getCoordinates(radius,8*baseWidth,radian);const specialConfig=this.getSpecialTickConfig(angle);if(specialConfig){ticksCtx.fillStyle=specialConfig.color;ticksCtx.fillText(specialConfig.alias,x,y)}else{ticksCtx.fillStyle=fillStyle;ticksCtx.fillText(angle.toString(),x,y)}});const arrowWidth=canvas.width/18;ticksCtx.translate(canvas.width/2,canvas.height/2);ticksCtx.scale(1,-1);ticksCtx.rotate((isNorthFacing?-yawAngle:0)*Math.PI/180);ticksCtx.lineJoin="round";ticksCtx.lineCap="round";ticksCtx.strokeStyle=fillStylePrimary;ticksCtx.fillStyle=fillStylePrimary;ticksCtx.lineWidth=arrowWidth/6;ticksCtx.beginPath();ticksCtx.moveTo(0,arrowWidth/2);ticksCtx.lineTo(arrowWidth/2,-arrowWidth/2);ticksCtx.lineTo(0,arrowWidth/2-1/((1+Math.sqrt(5))/2)*arrowWidth);ticksCtx.lineTo(-arrowWidth/2,-arrowWidth/2);ticksCtx.lineTo(0,arrowWidth/2);ticksCtx.fill();ticksCtx.stroke();this.state.ticksCanvas=ticksCanvas}drawBackground(){const{canvas,baseWidth,padding,fillStyleTransparentBase}=this.state;const BGCanvas=document.createElement("canvas");BGCanvas.width=canvas.width;BGCanvas.height=canvas.height;const BGCtx=BGCanvas.getContext("2d");if(!BGCtx)return;const radius=canvas.width/2;BGCtx.beginPath();BGCtx.arc(radius,radius,radius-2*baseWidth,0,2*Math.PI);BGCtx.strokeStyle=fillStyleTransparentBase;BGCtx.lineWidth=baseWidth;BGCtx.stroke();new Array(5).fill(1).forEach((_,i)=>{BGCtx.beginPath();BGCtx.arc(radius,radius,(radius-padding)*(i+1)/5,0,2*Math.PI);BGCtx.strokeStyle=fillStyleTransparentBase;BGCtx.lineWidth=baseWidth/4;BGCtx.stroke()});this.state.BGCanvas=BGCanvas}draw(){const{data,canvas,ctx,baseWidth,padding,BGCanvas,ticksCanvas,color2intensity:c2i,fillStylePrimary,fillStyleTransparentBase}=this.state;const{series,range,polygon,isNorthFacing=true,yawAngle=0}=data;if(!series&&!polygon)return;this.clearRect();const radius=canvas.width/2;const northYawgle=isNorthFacing?0:-yawAngle;const angleOffset=this.getAngleOffset();if(BGCanvas)ctx.drawImage(BGCanvas,0,0);if(range){const sliceAngle=360/range.length;range.forEach((value,index)=>{const startAngle=(index*sliceAngle+northYawgle+angleOffset)*Math.PI/180;const endAngle=((index+1)*sliceAngle+northYawgle+angleOffset)*Math.PI/180;ctx.beginPath();ctx.moveTo(radius,radius);ctx.arc(radius,radius,radius-padding,startAngle,endAngle);ctx.closePath();ctx.fillStyle=c2i[value];ctx.fill()})}if(polygon?.length){const orderedPolygons=[...polygon].sort((a,b)=>getMaxPolygonValue(b)-getMaxPolygonValue(a));for(const poly of orderedPolygons){const{values,color}=poly;if(!values||values.length<3)continue;const sliceAngle=360/values.length;const polygonColor=color??fillStylePrimary;ctx.beginPath();const vertices=[];values.forEach((value,index)=>{const angle=(index*sliceAngle+northYawgle+angleOffset)*Math.PI/180;const radial=Math.max(0,Math.min(1,value))*(radius-padding);const[x,y]=getCoordinates(radius,radius-radial,angle);vertices.push([x,y]);if(0===index)ctx.moveTo(x,y);else ctx.lineTo(x,y)});ctx.closePath();ctx.save();const gradient=ctx.createRadialGradient(radius,radius,0,radius,radius,radius-padding);gradient.addColorStop(0,"transparent");gradient.addColorStop(1,polygonColor);ctx.fillStyle=gradient;ctx.fill();ctx.strokeStyle=polygonColor;ctx.lineWidth=1;ctx.lineJoin="round";ctx.lineCap="round";ctx.stroke();ctx.fillStyle=polygonColor;const dotRadius=baseWidth/2;for(const[vx,vy]of vertices){ctx.beginPath();ctx.arc(vx,vy,dotRadius,0,2*Math.PI);ctx.fill()}ctx.restore()}}series?.forEach(({value,color,lineWidth=baseWidth,radio=1})=>{if(null==value)return;const pointerAngle=(value+northYawgle)*Math.PI/180-Math.PI/2;const[startX,startY]=getCoordinates(radius,radius,pointerAngle);const[endX,endY]=getCoordinates(radius,padding+(1-radio)*(radius-padding)+lineWidth/2,pointerAngle);if(1!==radio){ctx.beginPath();ctx.moveTo(...getCoordinates(radius,padding+lineWidth/2,pointerAngle));ctx.lineTo(endX,endY);ctx.strokeStyle=fillStyleTransparentBase;ctx.stroke()}ctx.beginPath();ctx.moveTo(endX,endY);ctx.lineTo(startX,startY);ctx.strokeStyle=color;ctx.lineWidth=lineWidth;ctx.lineJoin="round";ctx.lineCap="round";ctx.stroke()});if(ticksCanvas)ctx.drawImage(ticksCanvas,0,0)}}const SPECIAL_TICK_CONFIG={0:{color:DEFAULTS.DIAL_NORTH_COLOR,alias:"北"},180:{color:DEFAULTS.DIAL_SOUTH_COLOR,alias:"南"}};class Dial extends CircularBase{getSpecialTickConfig(angle){return SPECIAL_TICK_CONFIG[angle]}getAngleOffset(){return -90}}class Radar extends CircularBase{getSpecialTickConfig(_angle){}getAngleOffset(){return 0}}class IQ extends Engine{init(props){super.init(props);this.updateProps({lineColor:props.lineColor??DEFAULTS.LINE_COLOR,pointColor:props.pointColor??DEFAULTS.POINT_COLOR})}clear(){this.clearRect();this.updateProps({data:{IData:[],QData:[]}})}render(data){if(data.IData&&data.QData){this.state.data=data;this.draw()}}draw(){const{data,canvas,pointColor,lineColor,ctx}=this.state;if(!data)return;const{IData,QData}=data;if(0===IData.length||0===QData.length)return;const{width,height}=ctx.canvas;ctx.clearRect(0,0,canvas.width,canvas.height);const[minX,maxX]=getMinMax(IData);const[minY,maxY]=getMinMax([minX,maxX,...QData]);const xScale=width/(maxX-minX);const yScale=height/(maxY-minY);const pointRadius=2;const points=IData.map((xVal,i)=>{const x=(xVal-minX)*xScale;const y=(QData[i]-minY)*yScale;return{x,y}});ctx.beginPath();points.forEach((point,i)=>{ctx[0===i?"moveTo":"lineTo"](point.x,point.y)});ctx.strokeStyle=lineColor;ctx.lineWidth=1;ctx.stroke();points.forEach(point=>{ctx.beginPath();ctx.arc(point.x,point.y,pointRadius,0,2*Math.PI);ctx.fillStyle=pointColor;ctx.fill()})}}const PIXEL_STRIDE=4;const COLOR_LOOKUP_STEPS=256;const createColorLookup=colors=>{const lookup=[];for(let i=0;i<COLOR_LOOKUP_STEPS;i++){const t=i/(COLOR_LOOKUP_STEPS-1);const segmentCount=colors.length-1;const segment=Math.min(Math.floor(t*segmentCount),segmentCount-1);const localT=t*segmentCount-segment;const c1=parseColor(colors[segment]);const c2=parseColor(colors[segment+1]);lookup.push({r:Math.round(c1.r+(c2.r-c1.r)*localT),g:Math.round(c1.g+(c2.g-c1.g)*localT),b:Math.round(c1.b+(c2.b-c1.b)*localT)})}return lookup};const createClassicPixelLookup=()=>{const colorLookup=createColorLookup(EYE_DEFAULTS.CLASSIC_COLORS);const lookup=new Uint8ClampedArray((EYE_DEFAULTS.MAX_ACCUMULATION+1)*PIXEL_STRIDE);const maxColorIndex=colorLookup.length-1;for(let acc=1;acc<=EYE_DEFAULTS.MAX_ACCUMULATION;acc++){const intensity=Math.min(acc*EYE_DEFAULTS.CLASSIC_ALPHA_GAIN/255,1);const colorIdx=Math.min(Math.floor(intensity*maxColorIndex),maxColorIndex);const color=colorLookup[colorIdx];const idx=acc*PIXEL_STRIDE;lookup[idx]=color.r;lookup[idx+1]=color.g;lookup[idx+2]=color.b;lookup[idx+3]=Math.min(acc*EYE_DEFAULTS.CLASSIC_ALPHA_GAIN,255)}return lookup};const CLASSIC_PIXEL_LOOKUP=createClassicPixelLookup();class IQEye extends Engine{init(props){super.init(props);this.state.iqEyeMode=this.resolveMode(props.iqEyeMode);this.initColorLookup()}resize(){super.resize();const{width,height}=this.state.canvas;this.state.accumulator=new Uint16Array(width*height);this.state.imageData=this.state.ctx.createImageData(width,height)}clear(){this.clearRect();this.state.accumulator?.fill(0);this.updateProps({data:{IData:[],QData:[]}})}render(data){if(!data.IData?.length||!data.QData?.length)return;this.state.data=data;if(!this.state.accumulator)return;if("classic"===this.state.iqEyeMode)this.state.accumulator.fill(0);else this.applyDecay();this.accumulateData(data);this.draw()}draw(){if("classic"===this.state.iqEyeMode){this.drawClassic();return}this.drawModern()}drawClassic(){const{accumulator,imageData,ctx}=this.state;if(!accumulator||!imageData)return;const pixelData=imageData.data;for(let i=0;i<accumulator.length;i++){const acc=accumulator[i];const idx=i*PIXEL_STRIDE;if(acc>0){const lookupIndex=acc*PIXEL_STRIDE;pixelData[idx]=CLASSIC_PIXEL_LOOKUP[lookupIndex];pixelData[idx+1]=CLASSIC_PIXEL_LOOKUP[lookupIndex+1];pixelData[idx+2]=CLASSIC_PIXEL_LOOKUP[lookupIndex+2];pixelData[idx+3]=CLASSIC_PIXEL_LOOKUP[lookupIndex+3]}else pixelData[idx+3]=0}ctx.putImageData(imageData,0,0)}drawModern(){const{accumulator,imageData,ctx,colorLookup}=this.state;if(!accumulator||!imageData||!colorLookup)return;const pixelData=imageData.data;const maxAcc=EYE_DEFAULTS.MAX_ACCUMULATION;for(let i=0;i<accumulator.length;i++){const acc=accumulator[i];const idx=i*PIXEL_STRIDE;if(acc>0){const intensity=Math.min(acc/maxAcc,1);const colorIdx=Math.min(Math.floor(intensity*(colorLookup.length-1)),colorLookup.length-1);const color=colorLookup[colorIdx];pixelData[idx]=color.r;pixelData[idx+1]=color.g;pixelData[idx+2]=color.b;pixelData[idx+3]=Math.round(255*Math.min(2*intensity,1))}else pixelData[idx+3]=0}ctx.putImageData(imageData,0,0)}initColorLookup(){const colors="classic"===this.state.iqEyeMode?EYE_DEFAULTS.CLASSIC_COLORS:EYE_DEFAULTS.MODERN_COLORS;this.state.colorLookup=createColorLookup(colors)}resolveMode(mode){if("modern"===mode)return"modern";return EYE_DEFAULTS.MODE}applyDecay(){const{accumulator}=this.state;if(!accumulator)return;const decay=EYE_DEFAULTS.DECAY_FACTOR;for(let i=0;i<accumulator.length;i++)accumulator[i]=Math.floor(accumulator[i]*decay)}accumulateData(data){const{accumulator,canvas}=this.state;if(!accumulator)return;const{width,height}=canvas;const[yMin,yMax]=EYE_DEFAULTS.Y_RANGE;const rangeY=yMax-yMin;const segmentSize=EYE_DEFAULTS.SEGMENT_SIZE;const pointSpacing=width/(segmentSize-1);this.accumulateSegments(data.IData,yMin,rangeY,width,height,segmentSize,pointSpacing,accumulator);this.accumulateSegments(data.QData,yMin,rangeY,width,height,segmentSize,pointSpacing,accumulator)}accumulateSegments(data,yMin,rangeY,width,height,segmentSize,pointSpacing,accumulator){const segmentCount=Math.ceil(data.length/segmentSize);for(let seg=0;seg<segmentCount;seg++){let prevX=-1,prevY=-1;for(let i=0;i<segmentSize;i++){const idx=seg*segmentSize+i;if(idx>=data.length)break;const value=data[idx];if(void 0===value)continue;const x=Math.round(i*pointSpacing);const y=Math.round((value-yMin)/rangeY*(height-1));if(prevX>=0)this.drawLine(prevX,prevY,x,y,width,height,accumulator);prevX=x;prevY=y}}}drawLine(x0,y0,x1,y1,width,height,accumulator){const dx=Math.abs(x1-x0);const dy=Math.abs(y1-y0);const sx=x0<x1?1:-1;const sy=y0<y1?1:-1;let x=x0,y=y0,err=dx-dy;const maxAcc=EYE_DEFAULTS.MAX_ACCUMULATION;while(true){if(x>=0&&x<width&&y>=0&&y<height){const idx=y*width+x;if(accumulator[idx]<maxAcc)accumulator[idx]++}if(x===x1&&y===y1)break;const e2=2*err;if(e2>-dy){err-=dy;x+=sx}if(e2<dx){err+=dx;y+=sy}}}}export{ColorInterpolator,Dial,Fluorescence,enums_FluorescenceRenderMode as FluorescenceRenderMode,Gauge,enums_GraphicType as GraphicType,cartesian_Heatmap as Heatmap,HeatmapCanvas,HeatmapWebGL,IQ,IQEye,enums_OrientationType as OrientationType,Radar,enums_RendererType as RendererType,cartesian_Series as Series,SeriesCanvas,color2intensity,createHeatmap,createSeries,hexToRGBA,rgbToHex};
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CircularBase.d.ts","sourceRoot":"","sources":["../../../src/renderers/circular/CircularBase.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,mBAAmB,CAAC;AACvC,OAAO,KAAK,EACV,YAAY,
|
|
1
|
+
{"version":3,"file":"CircularBase.d.ts","sourceRoot":"","sources":["../../../src/renderers/circular/CircularBase.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,mBAAmB,CAAC;AACvC,OAAO,KAAK,EACV,YAAY,EAGZ,aAAa,EACb,SAAS,EACV,MAAM,aAAa,CAAC;AAGrB;;GAEG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,CAAC,MAAM,EAAE,MAAM,CAAC,CAIlB;AAWD;;GAEG;AACH,MAAM,CAAC,OAAO,CAAC,QAAQ,OAAO,YAAa,SAAQ,MAAM;IAC/C,KAAK,EAAE,aAAa,CAAC;IAE7B,mBAAmB;IACnB,SAAS,CAAC,oBAAoB,CAC5B,MAAM,EAAE,MAAM,GACb;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;IAI/C,yCAAyC;IACzC,SAAS,CAAC,cAAc,IAAI,MAAM;IAIlC,IAAI,CAAC,KAAK,EAAE,SAAS;IAoCrB,KAAK;IAIL,MAAM;IAMN,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC;IAkBlC,SAAS;IAqFT,cAAc;IAkCd,IAAI;CAgJL"}
|