@rfkit/renderer 0.2.3 → 0.4.0

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 CHANGED
@@ -1,9 +1,9 @@
1
1
  # rfkit-renderer
2
- Canvas/WebGL 渲染器集合,适用于射频和通用数据可视化。内置线谱、热力/荧光图、IQ/眼图、仪表盘、环形/雷达等组件,并提供 WebGL 版热力渲染以处理大矩阵数据。
2
+ Canvas/WebGL 渲染器集合,适用于射频和通用数据可视化。内置线谱、热力/荧光图、IQ/眼图、仪表盘、环形/雷达等组件,并提供 WebGL 热力渲染与 Canvas 自动降级。
3
3
 
4
4
  ## 主要能力
5
- - Canvas 2D:`Series`(Line/Bar/Area/Stepline)、`Heatmap`、`Fluorescence`、`Gauge`、`Dial`、`Radar`、`IQ`、`IQEye`
6
- - WebGL:`HeatmapWebGL`(大规模强度矩阵,颜色查表渲染)
5
+ - Canvas 2D:`Series`(Line/Bar/Area/Stepline)、`HeatmapCanvas`、`FluorescenceCanvas`、`Gauge`、`Dial`、`Radar`、`IQ`、`IQEye`
6
+ - WebGL:`HeatmapWebGL`、`FluorescenceWebGL`;统一入口 `Heatmap` / `Fluorescence` 在能力不足时自动降级到 Canvas
7
7
  - 工具与类型:`ColorInterpolator`、`GraphicType`、`OrientationType`、`AxisYRange`、`SeriesConfig` 等
8
8
 
9
9
  ## 安装
@@ -18,6 +18,7 @@ HTML 容器:
18
18
  ```html
19
19
  <div id="heatmap" style="width:640px;height:240px;"></div>
20
20
  <div id="series" style="width:640px;height:200px;"></div>
21
+ <div id="fluorescence" style="width:640px;height:240px;"></div>
21
22
  <div id="heatmap-webgl" style="width:640px;height:240px;"></div>
22
23
  ```
23
24
 
@@ -25,10 +26,13 @@ HTML 容器:
25
26
 
26
27
  ```ts
27
28
  import {
29
+ Fluorescence,
30
+ FluorescenceRenderMode,
28
31
  Heatmap,
29
- Series,
30
32
  HeatmapWebGL,
31
- GraphicType
33
+ GraphicType,
34
+ RendererType,
35
+ Series
32
36
  } from '@rfkit/renderer';
33
37
 
34
38
  const heatmap = new Heatmap({
@@ -54,6 +58,20 @@ sweep.render({
54
58
  color: '#00FFA3',
55
59
  data: Float32Array.from({ length: 512 }, (_, i) => -90 + Math.cos(i / 18) * 15)
56
60
  });
61
+
62
+ const fluorescence = new Fluorescence({
63
+ id: 'fluorescence',
64
+ range: [-20, 100],
65
+ display: true,
66
+ renderer: RendererType.WebGL,
67
+ fluorescenceRenderMode: FluorescenceRenderMode.Gaussian
68
+ });
69
+
70
+ fluorescence.render({
71
+ // 频率优先排列;每个频率点固定包含 -20..140 的 161 个电平计数。
72
+ fluorescenceData: new Uint32Array(frequencyPointCount * 161),
73
+ fluorescenceMaxCount: 100
74
+ });
57
75
  ```
58
76
 
59
77
  使用 WebGL 热力渲染(需要 WebGL2 支持):
@@ -71,10 +89,18 @@ heatmapGL.render(largeMatrix); // largeMatrix: number[][]
71
89
  ## 数据格式速览
72
90
  - `Series.render` 接收 `SeriesConfig`(必填 `name`,`data` 为 `Float32Array`,`type` 由 `GraphicType` 指定)。
73
91
  - `Heatmap` / `HeatmapWebGL` 接收 `number[][]`,内层数组表示一行数据。
74
- - `Fluorescence` 接收 `{ fluorescenceData: Map<number, number>[], fluorescenceMaxCount: number }`。
92
+ - `Fluorescence` 接收 `{ fluorescenceData: Uint32Array, fluorescenceMaxCount: number }`;数组按频率点优先排列,每个频率点固定包含 `-20..140` 的 161 个电平计数。
75
93
  - `IQ`/`IQEye` 接收 `{ IData: number[], QData: number[] }`。
76
94
  `IQEye` 额外支持 `iqEyeMode`(`'classic' | 'legacy' | 'modern'`),默认 `classic`:每帧重算、绿红显影;传 `modern` 可启用跨帧 decay 模式。
77
95
  - `Dial`/`Radar` 使用 `CircularData`;`Gauge` 使用 `{ value: number; limit?: number }`。
78
96
  `CircularData` 支持可选 `polygon: CircularPolygonItem[]` 绘制雷达多边形:每个多边形提供 `values`(各方向归一化值 0-1,长度即方向数,按 0..360 度均匀分布)、可选 `color`(缺省走主题主色)与可选 `showDots`(是否绘制顶点小圆点,缺省 true);各方向值决定半径,连成闭合多边形,径向渐变填充(中心透明→边缘主题色)+ 细描边 + 顶点实心圆点。支持多网叠加,绘制时按每网最大半径降序(大网下层、小网上层)。
79
97
 
80
98
  常用操作:`setRange(range)` 调整量程并重绘;`resize()` 在容器尺寸变化后调用;`clear()` 清空画布及内部数据。
99
+
100
+ WebGL 默认不保留绘图缓冲。DOM 截图工具需要同步取得当前帧时,可对目标 Canvas 调用 `captureCanvasFrame(canvas)`;受管 WebGL Canvas 返回方向已校正的 Data URL,普通 Canvas 返回 `null` 并继续使用截图工具的原生路径。
101
+
102
+ ```ts
103
+ import { captureCanvasFrame } from '@rfkit/renderer';
104
+
105
+ const frame = captureCanvasFrame(document.querySelector('canvas')!);
106
+ ```
@@ -0,0 +1,12 @@
1
+ /** 可在绘图缓冲释放前同步导出当前帧的 Canvas 渲染器。 */
2
+ export interface CanvasCaptureSource {
3
+ toDataURL(type?: string, quality?: number): string | null;
4
+ }
5
+ export declare const registerCanvasCapture: (canvas: HTMLCanvasElement, source: CanvasCaptureSource) => void;
6
+ export declare const unregisterCanvasCapture: (canvas: HTMLCanvasElement, source: CanvasCaptureSource) => void;
7
+ /**
8
+ * 导出受管 WebGL Canvas 的当前可见帧。
9
+ *
10
+ * 普通 Canvas 返回 null,交由截图工具按原有 DOM/CSS 路径处理。
11
+ */
12
+ export declare const captureCanvasFrame: (canvas: HTMLCanvasElement, type?: string, quality?: number) => string | null;
package/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  export { default as ColorInterpolator } from './color/ColorInterpolator';
2
2
  export { color2intensity, hexToRGBA, rgbToHex } from './color/ColorUtils';
3
- export type { IHeatmap, ISeries } from './renderers/cartesian';
4
- export { createHeatmap, createSeries, Fluorescence, Gauge, Heatmap, HeatmapCanvas, Series, SeriesCanvas } from './renderers/cartesian';
3
+ export { type CanvasCaptureSource, captureCanvasFrame } from './core/canvasCapture';
4
+ export type { FluorescenceRendererState, IFluorescence, IHeatmap, ISeries } from './renderers/cartesian';
5
+ export { createFluorescence, createHeatmap, createSeries, Fluorescence, FluorescenceCanvas, Gauge, Heatmap, HeatmapCanvas, Series, SeriesCanvas } from './renderers/cartesian';
5
6
  export { Dial, Radar } from './renderers/circular';
6
7
  export { IQ, IQEye } from './renderers/scatter';
7
- export { HeatmapWebGL } from './renderers/webgl';
8
+ export { FluorescenceWebGL, HeatmapWebGL } from './renderers/webgl';
8
9
  export { type AxisYRange, type CircularData, type CircularPolygonItem, FluorescenceRenderMode, type GaugeData, GraphicType, type IQData, type IQEyeRenderMode, OrientationType, RendererType, type SeriesConfig, type StateProps } from './types';
package/index.js CHANGED
@@ -1,4 +1,266 @@
1
- let t=new Map;function e(t,e){let a,r,i,l;if(!/^#([A-Fa-f0-9]{3}){1,2}([A-Fa-f0-9]{2})?$/.test(t))throw Error("Invalid hex color format");let s=t.replace("#","");if(3===s.length)a=Number.parseInt(s[0]+s[0],16),r=Number.parseInt(s[1]+s[1],16),i=Number.parseInt(s[2]+s[2],16),l=255;else if(6===s.length)a=Number.parseInt(s.substring(0,2),16),r=Number.parseInt(s.substring(2,4),16),i=Number.parseInt(s.substring(4,6),16),l=255;else if(8===s.length)a=Number.parseInt(s.substring(0,2),16),r=Number.parseInt(s.substring(2,4),16),i=Number.parseInt(s.substring(4,6),16),l=Number.parseInt(s.substring(6,8),16);else throw Error("Invalid hex color length");return"number"==typeof e&&(l=Math.round(255*Math.max(0,Math.min(1,e)))),{r:a,g:r,b:i,a:l}}function a(t,e,r,i=255){let l=t=>t.toString(16).padStart(2,"0"),s=`#${l(t)}${l(e)}${l(r)}`;return i<255?`${s}${l(i)}`:s}function r(e){if("string"==typeof e){let a=t.get(e);if(a)return a;let r=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i.exec(e),i=r?{r:parseInt(r[1],16),g:parseInt(r[2],16),b:parseInt(r[3],16),a:r[4]?parseInt(r[4],16):255}:{r:0,g:0,b:0,a:255};return t.set(e,i),i}return{r:e.r,g:e.g,b:e.b,a:e.a??255}}function i(t,e=100){let a=[];for(let r=0;r<=e;r++){let i=Math.round(r/e*255).toString(16).padStart(2,"0").toUpperCase(),l=`${t}${i}`;a.push(l)}return a}function l(){t.clear()}class s{colorsPresetList=[];range=[0,0,0];static transparent={r:0,g:0,b:0,a:0};constructor(t,e,a,r=.1){this.setColors(t,e,a,r)}setColors(t,e,a,i){this.colorsPresetList=[];let l=Math.max(1,Math.floor((a-e)/i)),s=a-e;this.range=[e,a,s];let n=t.map(r);for(let t=0;t<=l;t++){let a=(e+t*i-e)/s;this.colorsPresetList.push(this.interpolateColor(n,a))}}interpolateColor(t,e){let r=Math.floor(e*(t.length-1)),i=e*(t.length-1)-r,l=t[r],s=t[r+1]||t[r],n={r:Math.round(l.r+i*(s.r-l.r)),g:Math.round(l.g+i*(s.g-l.g)),b:Math.round(l.b+i*(s.b-l.b)),a:Math.round(l.a+i*(s.a-l.a))};return{...n,hax:a(n.r,n.g,n.b,n.a)}}getColor(t){if(!Number.isFinite(t))return s.transparent;let[e,,a]=this.range,r=(t-e)/a;if(r<=0)return this.colorsPresetList[0];if(r>=1)return this.colorsPresetList[this.colorsPresetList.length-1];let i=Math.min(Math.floor(r*this.colorsPresetList.length),this.colorsPresetList.length-1);return this.colorsPresetList[i]}}class n{state;constructor(t){this.state={id:t.id,container:void 0,canvas:null,ctx:null,range:t.range??[-20,100]},this.init(t)}updateProps(t){this.state={...this.state,...t}}init(t){let{id:e}=this.state,a=document.getElementById(e),r=document.createElement("canvas"),i=r.getContext?.("2d");r.style.transform="scaleY(-1)",i&&(i.imageSmoothingEnabled=!1,i.lineJoin="miter",i.lineCap="butt",i.textBaseline="middle"),i&&this.updateProps({container:a??void 0,canvas:r,ctx:i}),a?.appendChild(r),this.resize(!1)}clearRect(){let{ctx:t,canvas:e}=this.state;t.clearRect(0,0,e.width,e.height)}resize(t=!0){let{canvas:e,container:a}=this.state,{clientWidth:r,clientHeight:i}=a;r&&i&&(e.width=r,e.height=i,t&&setTimeout(()=>{this.draw()}))}draw(){}dispose(){let{canvas:t,container:e}=this.state;e?.removeChild(t)}}let o={COLOR:"#000000",THICKNESS:1,FILL_STYLE:"#ffffffB0",FILL_STYLE_PRIMARY:"#1890ff",FILL_STYLE_TRANSPARENT_BASE:"#ffffff10",LINE_COLOR:"#00ff00",POINT_COLOR:"#ff0000",DIAL_NORTH_COLOR:"#ff4d4f",DIAL_SOUTH_COLOR:"#1890ff",FLUORESCENCE_COLORS:["#000080","#0000FF","#00FFFF","#00FF00","#FFFF00","#FF0000"],RANGE:[-20,100]},h={LEVEL_MIN:-20,LEVEL_MAX:140,LEVEL_RANGE:161,MAX_GAUSSIAN_RADIUS:30},u={Y_RANGE:[-1,1],SEGMENT_SIZE:5,MODE:"classic",DECAY_FACTOR:.98,MAX_ACCUMULATION:1e3,MODERN_COLORS:["#000080","#0000FF","#00FFFF","#00FF00","#FFFF00","#FF0000"],CLASSIC_COLORS:["#00ff00","#ff0000"],CLASSIC_ALPHA_GAIN:50};var c=/*#__PURE__*/function(t){return t.Horizontal="horizontal",t.Vertical="vertical",t}({}),d=/*#__PURE__*/function(t){return t.Circle="circle",t.Rect="rect",t.Line="line",t.Stepline="stepline",t.Bar="bar",t.Area="area",t}({}),f=/*#__PURE__*/function(t){return t.Canvas="canvas",t.WebGL="webgl",t}({}),g=/*#__PURE__*/function(t){return t.Gaussian="gaussian",t.Grid="grid",t}({});class p extends n{init(t){super.init(t);let{colors:e,display:a,fluorescenceRenderMode:r}=t;this.updateProps({colors:e,data:new Uint32Array(0),display:a,renderMode:r??g.Gaussian}),this.resize()}updateProps(t){let e=t.range&&(t.range[0]!==this.state.range?.[0]||t.range[1]!==this.state.range?.[1]),a=void 0!==t.renderMode&&t.renderMode!==this.state.renderMode;super.updateProps(t),(e||a)&&this.state.data?.length>0&&this.draw()}setRenderMode(t){this.updateProps({renderMode:t})}clearImageData(){let{ctx:t,canvas:{height:e,width:a}}=this.state;e&&a&&this.updateProps({imageData:t.createImageData(a,e)})}clearRect(){super.clearRect(),this.clearImageData()}clear(){this.clearRect(),this.updateProps({data:new Uint32Array(0)})}dispose(){this.intensityMatrixCache=null,this.gridCentersCache=null,this.weightLookupCache=null,this.colorLookupCache=null,this.colorCache.clear(),this.lastRenderParams=null,this.blockPositionsCache=null,this.lastBlockRenderParams=null}drawBlocks(){let{imageData:t,data:e,canvas:a,ctx:r,colors:i=o.FLUORESCENCE_COLORS,range:l=o.RANGE}=this.state;if(!e||0===e.length||!t)return;let{width:s,height:n}=a,[u,c]=l,d=c-u;if(d<=0)return;let f=e.length/h.LEVEL_RANGE;if(!Number.isInteger(f)||f<=0)return;let g={dataLength:f,width:s,height:n,rangeMin:u,rangeMax:c};if(!this.lastBlockRenderParams||Object.keys(g).some(t=>this.lastBlockRenderParams?.[t]!==g[t])||!this.blockPositionsCache||this.blockPositionsCache.length!==f){this.blockPositionsCache=Array(f);let t=Math.max(1,Math.floor(s/f));for(let e=0;e<f;e++){let a=Math.floor(e*s/f),r=Math.min(s-1,a+t-1);this.blockPositionsCache[e]={startX:a,endX:r,width:r-a+1}}}this.lastBlockRenderParams=g;let p=t.data;p.fill(0);let m=1/d,T=n-1,C=Math.max(1,Math.ceil(n/d)),E=0;for(let t=0;t<f;t++){let a=t*h.LEVEL_RANGE;for(let t=0;t<h.LEVEL_RANGE;t++){let r=e[a+t];if(r<=0)continue;let i=t+h.LEVEL_MIN;i<u||i>c||!(r>E)||(E=r)}}let M=Math.log(Math.max(E,1)+1);for(let t=0;t<f;t++){let{startX:a,endX:r}=this.blockPositionsCache[t],l=t*h.LEVEL_RANGE;for(let t=0;t<h.LEVEL_RANGE;t++){let n=e[l+t];if(n<=0)continue;let o=t+h.LEVEL_MIN;if(o<u||o>c)continue;let d=Math.max(0,Math.floor((o-u)*m*T-C/2)),f=Math.min(T,d+C-1),g=Math.log(n+1)/M,E=Math.round(255*g),{r:R,g:L,b:A}=this.interpolateColor(i,g);for(let t=d;t<=f;t++){let e=t*s;for(let t=a;t<=r;t++){let a=(e+t)*4;p[a]=R,p[a+1]=L,p[a+2]=A,p[a+3]=E}}}}r.putImageData(t,0,0)}resize(){super.resize(),this.clearImageData()}setRange(t){t&&(this.updateProps({range:t}),this.draw())}render(t){t?.fluorescenceData?.length>=0&&((this.state.data?.length??0)!==t.fluorescenceData.length&&(this.clearImageData(),this.intensityMatrixCache=null,this.gridCentersCache=null,this.blockPositionsCache=null,this.lastRenderParams=null,this.lastBlockRenderParams=null),this.state.data=t.fluorescenceData,this.state.fluorescenceMaxCount=t.fluorescenceMaxCount,this.draw())}interpolateColor(t,e){if(!t||0===t.length)return{r:255,g:255,b:255,a:255};e=Math.max(0,Math.min(1,e));let a=t=>"string"==typeof t?this.hexToRgb(t):{r:t.r,g:t.g,b:t.b};if(1===t.length||0===e)return{...a(t[0]),a:255};if(1===e)return{...a(t[t.length-1]),a:255};let r=e*(t.length-1),i=Math.floor(r),l=r-i,s=Math.min(i,t.length-2),n=a(t[s]),o=a(t[s+1]);return{r:Math.round(n.r+(o.r-n.r)*l),g:Math.round(n.g+(o.g-n.g)*l),b:Math.round(n.b+(o.b-n.b)*l),a:255}}colorCache=new Map;hexToRgb(t){let e=this.colorCache.get(t);if(e)return e;let a=r(t),i={r:a.r,g:a.g,b:a.b};return this.colorCache.set(t,i),i}lastRenderParams=null;intensityMatrixCache=null;gridCentersCache=null;weightLookupCache=null;colorLookupCache=null;blockPositionsCache=null;lastBlockRenderParams=null;draw(){let{imageData:t,data:e,canvas:a,ctx:r,colors:i=o.FLUORESCENCE_COLORS,range:l=o.RANGE,fluorescenceMaxCount:s=1,display:n,renderMode:u=g.Gaussian}=this.state;if(!n||!e||0===e.length||!t)return;if(u===g.Grid){this.drawBlocks();return}let{width:c,height:d}=a,[f,p]=l,m=p-f;if(m<=0)return;let T=e.length/h.LEVEL_RANGE;if(!Number.isInteger(T)||T<=0)return;let C={dataLength:T,rangeMin:f,rangeMax:p,width:c,height:d,maxCount:s},E=!this.lastRenderParams||this.lastRenderParams.dataLength!==C.dataLength||this.lastRenderParams.width!==C.width,M=!this.lastRenderParams||this.lastRenderParams.height!==C.height,R=!this.colorLookupCache;this.lastRenderParams=C;let L=t.data;L.fill(0);let A=Math.min(Math.max(2,Math.ceil(d/m),Math.floor(d/50)),h.MAX_GAUSSIAN_RADIUS),S=A*A,_=c*d,I=Math.min(Math.max(A,Math.ceil(c/T)),h.MAX_GAUSSIAN_RADIUS);this.intensityMatrixCache&&this.intensityMatrixCache.length===_?this.intensityMatrixCache.fill(0):this.intensityMatrixCache=new Float32Array(_);let P=this.intensityMatrixCache;if(E||!this.gridCentersCache||this.gridCentersCache.length!==T){this.gridCentersCache=new Float32Array(T);let t=c/T;for(let e=0;e<T;e++){let a=Math.floor(t*e),r=Math.ceil(t*(e+1));this.gridCentersCache[e]=(a+r)/2}}let x=this.gridCentersCache,b=Math.ceil(10*A)+1;if(M||!this.weightLookupCache||this.weightLookupCache.length!==b){this.weightLookupCache=new Float32Array(b);for(let t=0;t<=10*A;t++){let e=t/10;this.weightLookupCache[t]=Math.exp(-(e*e)/(2*S))}}let w=this.weightLookupCache,y=0;for(let t=0;t<T;t++){let a=x[t],r=t*h.LEVEL_RANGE;for(let t=0;t<h.LEVEL_RANGE;t++){let i=e[r+t];if(i<=0)continue;let l=t+h.LEVEL_MIN;if(l<f||l>p)continue;let n=(l-f)/m*(d-1),o=Math.log(i+1)/Math.log(Math.max(s,1)+1),u=Math.max(0,Math.floor(a-I)),g=Math.min(c-1,Math.ceil(a+I)),T=Math.max(0,Math.floor(n-A)),C=Math.min(d-1,Math.ceil(n+A));for(let t=T;t<=C;t++){let e=t-n,r=e*e,i=t*c;for(let t=u;t<=g;t++){let e=(t-a)*A/I,l=e*e+r;if(l>S)continue;let s=Math.floor(10*Math.sqrt(l)),n=s<w.length?w[s]:0,h=i+t,u=P[h]+o*n;P[h]=u,u>y&&(y=u)}}}}let N=y>0?1/y:0;if(R||!this.colorLookupCache||256!==this.colorLookupCache.length){this.colorLookupCache=Array(256);for(let t=0;t<256;t++){let e=t/255;this.colorLookupCache[t]=this.interpolateColor(i,e)}}let v=this.colorLookupCache;for(let t=0;t<P.length;t++){let e=P[t];if(e<=.001)continue;let a=Math.min(e*N,1),r=v[Math.min(Math.floor(255*a),255)],i=4*t;L[i]=r.r,L[i+1]=r.g,L[i+2]=r.b,L[i+3]=Math.round(255*a)}r.putImageData(t,0,0)}}function m(t,e,a,r,i){if(!a)return;let l=a.length,s=e/l,n=0;for(let e=0;e<l;e++){let l=a[e],o=l.length;if(0===o)continue;0===n&&(n=t/o);let h=Math.floor(s*e),u=Math.ceil(s*(e+1));for(let e=0;e<o;e++){let a=l[e],s=Math.floor(n*e),o=Math.ceil(n*(e+1)),c=i(a);for(let e=h;e<u;e++){let a=e*t;for(let t=s;t<o;t++){let e=(a+t)*4;r[e]=c.r,r[e+1]=c.g,r[e+2]=c.b,r[e+3]=c.a}}}}}function T(t){if(!Array.isArray(t)||0===t.length)throw Error("Input must be a non-empty array.");let e=t[0],a=t[0];for(let r=1;r<t.length;r++)t[r]<e?e=t[r]:t[r]>a&&(a=t[r]);return[e,a]}function C(t){return"number"==typeof t&&Number.isFinite(t)}function E(t){return new Float64Array(t.flatMap(e=>0===e.length?Array(t[0].length).fill(Number.NaN):e))}let M=null;function R(){if(null!==M)return M;try{let t=document.createElement("canvas").getContext("webgl2");if(M=!!t,t){let e=t.getExtension("WEBGL_lose_context");e?.loseContext()}}catch{M=!1}return M}class L extends n{init(t){super.init(t),this.state.canvas.style.transform="scaleY(1)";let e=t.fillStyle??o.FILL_STYLE,a=t.fillStylePrimary??o.FILL_STYLE_PRIMARY,r=t.fillStyleTransparentBase??o.FILL_STYLE_TRANSPARENT_BASE;this.updateProps({fillStyle:e,fillStylePrimary:a,fillStyleTransparentBase:r,baseWidth:6,padding:t.padding??0,step:t.ticksStep??10,lineWidth:1,data:{value:0,limit:0}})}clear(){this.clearRect();let{ctx:t,BGCanvas:e}=this.state;e&&t.drawImage(e,0,0)}resize(){super.resize(),this.state.lineWidth&&this.drawBackground()}render(t){this.state.data={...this.state.data,...t},this.draw()}draw(){let{data:{value:t},range:[e,a],canvas:r,ctx:i,baseWidth:l,padding:s,BGCanvas:n,fillStylePrimary:o}=this.state;this.clearRect(),n&&i.drawImage(n,0,0);let h=3*l,u=r.height-2*s,c=(r.width-h)/2,d=(t-e)/(a-e)*u;i.fillStyle=o,i.fillRect(c,u-d+s,h,d),this.drawLimit()}drawLimit(){let{data:{limit:t},range:[e,a],canvas:r,ctx:i,baseWidth:l,padding:s,lineWidth:n,fillStylePrimary:o}=this.state;if(!C(t))return;let h=3*l,u=r.height-2*s,c=(r.width-h)/2,d=u*(a-t)/(a-e)+s;i.beginPath(),i.moveTo(c+h,d),i.lineTo(c+h+9,d-9),i.lineTo(c+h+9+30,d-9),i.lineTo(c+h+9+30,d+9),i.lineTo(c+h+9,d+9),i.lineTo(c+h,d),i.lineTo(c,d),i.lineWidth=n,i.strokeStyle=o,i.stroke(),i.fillStyle=o,i.font="12px Arial",i.textAlign="center",i.textBaseline="middle",i.fillText(t.toString(),c+h+9+15,d+1)}drawBackground(){let{canvas:t,baseWidth:e,padding:a,lineWidth:r,step:i,range:[l,s],fillStyle:n,fillStyleTransparentBase:o}=this.state,h=document.createElement("canvas");h.width=t.width,h.height=t.height;let u=h.getContext("2d");if(!u)return;let c=3*e,d=t.height-2*a,f=(t.width-c)/2,g=Array.from({length:11},(t,e)=>l+Math.round(e*(s-l)/10/i)*i);u.fillStyle=o,u.fillRect(f,a,c,d);for(let t=l;t<=s;t+=i){let i=g.includes(t),o=d*(s-t)/(s-l)+a;u.beginPath(),u.moveTo(f-(i?e:e/2),o),u.lineTo(f,o),u.strokeStyle=n,u.lineWidth=i?r:r/2,u.stroke(),i&&(u.fillStyle=n,u.font="12px Arial",u.textAlign="right",u.textBaseline="middle",u.fillText(t.toString(),f-1.2*e,o))}this.state.BGCanvas=h}}class A{state;constructor(t){this.state={id:t.id,container:void 0,canvas:null,gl:null,range:t.range??[-20,100]},this.init(t)}updateProps(t){this.state={...this.state,...t}}init(t){let{id:e}=this.state,a=document.getElementById(e),r=document.createElement("canvas"),i=r.getContext("webgl2",{alpha:!0,antialias:!1,premultipliedAlpha:!0,preserveDrawingBuffer:!1});if(!i){console.error("WebGL 2.0 not supported");return}r.style.transform="scaleY(-1)",this.updateProps({container:a??void 0,canvas:r,gl:i}),a?.appendChild(r),this.resize(!1)}compileShader(t,e){let{gl:a}=this.state,r=a.createShader(t);return r?(a.shaderSource(r,e),a.compileShader(r),a.getShaderParameter(r,a.COMPILE_STATUS))?r:(console.error("Shader compile error:",a.getShaderInfoLog(r)),a.deleteShader(r),null):null}createProgram(t){let{gl:e}=this.state,a=this.compileShader(e.VERTEX_SHADER,t.vertex),r=this.compileShader(e.FRAGMENT_SHADER,t.fragment);if(!a||!r)return null;let i=e.createProgram();return i?(e.attachShader(i,a),e.attachShader(i,r),e.linkProgram(i),e.getProgramParameter(i,e.LINK_STATUS))?(e.deleteShader(a),e.deleteShader(r),i):(console.error("Program link error:",e.getProgramInfoLog(i)),e.deleteProgram(i),null):null}createTexture(){let{gl:t}=this.state,e=t.createTexture();return e?(t.bindTexture(t.TEXTURE_2D,e),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.NEAREST),e):null}createQuadBuffer(){let{gl:t}=this.state,e=t.createBuffer();if(!e)return null;t.bindBuffer(t.ARRAY_BUFFER,e);let a=new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]);return t.bufferData(t.ARRAY_BUFFER,a,t.STATIC_DRAW),e}clearRect(){let{gl:t}=this.state;t.clearColor(0,0,0,0),t.clear(t.COLOR_BUFFER_BIT)}resize(t=!0){let{canvas:e,container:a,gl:r}=this.state;if(!a)return;let{clientWidth:i,clientHeight:l}=a;i&&l&&(e.width=i,e.height=l,r.viewport(0,0,i,l),t&&setTimeout(()=>{this.draw()}))}draw(){}dispose(){let{gl:t,canvas:e}=this.state;if(t){let e=t.getExtension("WEBGL_lose_context");e&&e.loseContext()}e?.remove()}}let S=`#version 300 es
1
+ let e=new Map;function t(e,t){let a,r,i,n;if(!/^#([A-Fa-f0-9]{3}){1,2}([A-Fa-f0-9]{2})?$/.test(e))throw Error("Invalid hex color format");let l=e.replace("#","");if(3===l.length)a=Number.parseInt(l[0]+l[0],16),r=Number.parseInt(l[1]+l[1],16),i=Number.parseInt(l[2]+l[2],16),n=255;else if(6===l.length)a=Number.parseInt(l.substring(0,2),16),r=Number.parseInt(l.substring(2,4),16),i=Number.parseInt(l.substring(4,6),16),n=255;else if(8===l.length)a=Number.parseInt(l.substring(0,2),16),r=Number.parseInt(l.substring(2,4),16),i=Number.parseInt(l.substring(4,6),16),n=Number.parseInt(l.substring(6,8),16);else throw Error("Invalid hex color length");return"number"==typeof t&&(n=Math.round(255*Math.max(0,Math.min(1,t)))),{r:a,g:r,b:i,a:n}}function a(e,t,r,i=255){let n=e=>e.toString(16).padStart(2,"0"),l=`#${n(e)}${n(t)}${n(r)}`;return i<255?`${l}${n(i)}`:l}function r(t){if("string"==typeof t){let a=e.get(t);if(a)return a;let r=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i.exec(t),i=r?{r:parseInt(r[1],16),g:parseInt(r[2],16),b:parseInt(r[3],16),a:r[4]?parseInt(r[4],16):255}:{r:0,g:0,b:0,a:255};return e.set(t,i),i}return{r:t.r,g:t.g,b:t.b,a:t.a??255}}function i(e,t=100){let a=[];for(let r=0;r<=t;r++){let i=Math.round(r/t*255).toString(16).padStart(2,"0").toUpperCase(),n=`${e}${i}`;a.push(n)}return a}function n(){e.clear()}class l{colorsPresetList=[];range=[0,0,0];static transparent={r:0,g:0,b:0,a:0};constructor(e,t,a,r=.1){this.setColors(e,t,a,r)}setColors(e,t,a,i){this.colorsPresetList=[];let n=Math.max(1,Math.floor((a-t)/i)),l=a-t;this.range=[t,a,l];let o=e.map(r);for(let e=0;e<=n;e++){let a=(t+e*i-t)/l;this.colorsPresetList.push(this.interpolateColor(o,a))}}interpolateColor(e,t){let r=Math.floor(t*(e.length-1)),i=t*(e.length-1)-r,n=e[r],l=e[r+1]||e[r],o={r:Math.round(n.r+i*(l.r-n.r)),g:Math.round(n.g+i*(l.g-n.g)),b:Math.round(n.b+i*(l.b-n.b)),a:Math.round(n.a+i*(l.a-n.a))};return{...o,hax:a(o.r,o.g,o.b,o.a)}}getColor(e){if(!Number.isFinite(e))return l.transparent;let[t,,a]=this.range,r=(e-t)/a;if(r<=0)return this.colorsPresetList[0];if(r>=1)return this.colorsPresetList[this.colorsPresetList.length-1];let i=Math.min(Math.floor(r*this.colorsPresetList.length),this.colorsPresetList.length-1);return this.colorsPresetList[i]}}let o=(e,t)=>{e.__rfkitCanvasCapture=t},s=(e,t)=>{e.__rfkitCanvasCapture===t&&delete e.__rfkitCanvasCapture},h=(e,t="image/png",a)=>{let r=e.__rfkitCanvasCapture??e.__rfkitHeatmap;return r?.toDataURL(t,a)??null},u={COLOR:"#000000",THICKNESS:1,FILL_STYLE:"#ffffffB0",FILL_STYLE_PRIMARY:"#1890ff",FILL_STYLE_TRANSPARENT_BASE:"#ffffff10",LINE_COLOR:"#00ff00",POINT_COLOR:"#ff0000",DIAL_NORTH_COLOR:"#ff4d4f",DIAL_SOUTH_COLOR:"#1890ff",FLUORESCENCE_COLORS:["#000080","#0000FF","#00FFFF","#00FF00","#FFFF00","#FF0000"],RANGE:[-20,100]},d={LEVEL_MIN:-20,LEVEL_MAX:140,LEVEL_RANGE:161,MAX_GAUSSIAN_RADIUS:30},c={Y_RANGE:[-1,1],SEGMENT_SIZE:5,MODE:"classic",DECAY_FACTOR:.98,MAX_ACCUMULATION:1e3,MODERN_COLORS:["#000080","#0000FF","#00FFFF","#00FF00","#FFFF00","#FF0000"],CLASSIC_COLORS:["#00ff00","#ff0000"],CLASSIC_ALPHA_GAIN:50};var f=/*#__PURE__*/function(e){return e.Horizontal="horizontal",e.Vertical="vertical",e}({}),g=/*#__PURE__*/function(e){return e.Circle="circle",e.Rect="rect",e.Line="line",e.Stepline="stepline",e.Bar="bar",e.Area="area",e}({}),p=/*#__PURE__*/function(e){return e.Canvas="canvas",e.WebGL="webgl",e}({}),m=/*#__PURE__*/function(e){return e.Gaussian="gaussian",e.Grid="grid",e}({});function E(e,t,a,r,i){if(r.fill(0),!a)return;let n=a.length;if(0===n)return;let l=t/n,o=0;for(let t=0;t<n;t++){let n=a[t],s=n.length;if(0===s)continue;0===o&&(o=e/s);let h=Math.floor(l*t),u=Math.ceil(l*(t+1));for(let t=0;t<s;t++){let a=n[t],l=Math.floor(o*t),s=Math.ceil(o*(t+1)),d=i(a);for(let t=h;t<u;t++){let a=t*e;for(let e=l;e<s;e++){let t=(a+e)*4;r[t]=d.r,r[t+1]=d.g,r[t+2]=d.b,r[t+3]=d.a}}}}}function T(e){if(!Array.isArray(e)||0===e.length)throw Error("Input must be a non-empty array.");let t=e[0],a=e[0];for(let r=1;r<e.length;r++)e[r]<t?t=e[r]:e[r]>a&&(a=e[r]);return[t,a]}function _(e){return"number"==typeof e&&Number.isFinite(e)}function x(e){return new Float64Array(e.flatMap(t=>0===t.length?Array(e[0].length).fill(Number.NaN):t))}let M=null;function C(){if(null!==M)return M;try{let e=document.createElement("canvas").getContext("webgl2");if(M=!!e,e){let t=e.getExtension("WEBGL_lose_context");t?.loseContext()}}catch{M=!1}return M}let R=256,b=4.5,L=Math.exp(-4.5),v=1.3,A=1/(1-L),y=(e,t,a,r,i)=>{let n=i-r,l=a/n,o=Math.min(Math.max(2,Math.ceil(l),Math.floor(a/50)),d.MAX_GAUSSIAN_RADIUS),s=t/e,h=Math.min(Math.max(o,Math.ceil(s)),d.MAX_GAUSSIAN_RADIUS);return{cellWidth:s,radiusX:h,radiusY:o,rangeSpan:n,xSampleCount:Math.ceil((2*h+2)/s)+2,ySampleCount:Math.ceil((2*o+2)/l)+2}},w=(e,t)=>{if(0===e.length)return{r:255,g:255,b:255,a:255};let a=Math.max(0,Math.min(1,t))*(e.length-1),i=Math.min(Math.floor(a),e.length-1),n=Math.min(i+1,e.length-1),l=a-i,o=r(e[i]),s=r(e[n]);return{r:Math.round(o.r+(s.r-o.r)*l),g:Math.round(o.g+(s.g-o.g)*l),b:Math.round(o.b+(s.b-o.b)*l),a:Math.round(o.a+(s.a-o.a)*l)}},F=e=>{let t=new Uint8Array(1024);for(let a=0;a<256;a++){let r=w(e,a/255),i=4*a;t[i]=r.r,t[i+1]=r.g,t[i+2]=r.b,t[i+3]=255}return t},S=(e,t,a,r)=>{let i=Math.max(0,Math.ceil(a-d.LEVEL_MIN)),n=Math.min(d.LEVEL_RANGE-1,Math.floor(r-d.LEVEL_MIN)),l=0;for(let a=0;a<t;a++){let t=a*d.LEVEL_RANGE;for(let a=i;a<=n;a++)l=Math.max(l,e[t+a])}return Math.max(l,1)};class I{state;constructor(e){this.state={id:e.id,container:void 0,canvas:null,gl:null,range:e.range??[-20,100]},this.init(e)}updateProps(e){this.state={...this.state,...e}}init(e){let{id:t}=this.state,a=document.getElementById(t),r=document.createElement("canvas"),i=r.getContext("webgl2",{alpha:!0,antialias:!1,premultipliedAlpha:!0,preserveDrawingBuffer:!1});if(!i)throw Error("WebGL 2.0 is not available");r.style.transform="scaleY(-1)",this.updateProps({container:a??void 0,canvas:r,gl:i}),a?.appendChild(r),this.resize(!1)}compileShader(e,t){let{gl:a}=this.state,r=a.createShader(e);return r?(a.shaderSource(r,t),a.compileShader(r),a.getShaderParameter(r,a.COMPILE_STATUS))?r:(console.warn("Shader compile error:",a.getShaderInfoLog(r)),a.deleteShader(r),null):null}createProgram(e){let{gl:t}=this.state,a=this.compileShader(t.VERTEX_SHADER,e.vertex),r=this.compileShader(t.FRAGMENT_SHADER,e.fragment);if(!a||!r)return a&&t.deleteShader(a),r&&t.deleteShader(r),null;let i=t.createProgram();return i?(t.attachShader(i,a),t.attachShader(i,r),t.linkProgram(i),t.getProgramParameter(i,t.LINK_STATUS))?(t.deleteShader(a),t.deleteShader(r),i):(console.warn("Program link error:",t.getProgramInfoLog(i)),t.deleteShader(a),t.deleteShader(r),t.deleteProgram(i),null):(t.deleteShader(a),t.deleteShader(r),null)}createTexture(){let{gl:e}=this.state,t=e.createTexture();return t?(e.bindTexture(e.TEXTURE_2D,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.NEAREST),t):null}createQuadBuffer(){let{gl:e}=this.state,t=e.createBuffer();if(!t)return null;e.bindBuffer(e.ARRAY_BUFFER,t);let a=new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]);return e.bufferData(e.ARRAY_BUFFER,a,e.STATIC_DRAW),t}clearRect(){let{gl:e}=this.state;e.clearColor(0,0,0,0),e.clear(e.COLOR_BUFFER_BIT)}resize(e=!0){let{canvas:t,container:a,gl:r}=this.state;if(!a)return;let{clientWidth:i,clientHeight:n}=a;i&&n&&(t.width=i,t.height=n,r.viewport(0,0,i,n),e&&setTimeout(()=>{this.draw()}))}draw(){}dispose(){let{gl:e,canvas:t}=this.state;if(e){let t=e.getExtension("WEBGL_lose_context");t&&t.loseContext()}t?.remove()}}let k=256,D=`#version 300 es
2
+ in vec2 a_position;
3
+ out vec2 v_texCoord;
4
+
5
+ void main() {
6
+ gl_Position = vec4(a_position, 0.0, 1.0);
7
+ v_texCoord = a_position * 0.5 + 0.5;
8
+ }
9
+ `,P=`
10
+ const float GAUSSIAN_EDGE_EXPONENT = 4.5;
11
+ const float GAUSSIAN_EDGE_WEIGHT = 0.0111089965;
12
+
13
+ float gaussianWeight(float normalizedDistanceSquared) {
14
+ if (normalizedDistanceSquared >= 1.0) return 0.0;
15
+ float quantized = floor(normalizedDistanceSquared * 255.0) / 255.0;
16
+ return max(
17
+ 0.0,
18
+ (exp(-GAUSSIAN_EDGE_EXPONENT * quantized) - GAUSSIAN_EDGE_WEIGHT) /
19
+ (1.0 - GAUSSIAN_EDGE_WEIGHT)
20
+ );
21
+ }
22
+ `,N=`#version 300 es
23
+ precision highp float;
24
+ precision highp int;
25
+ precision highp usampler2D;
26
+
27
+ layout(location = 0) out vec2 horizontalValue;
28
+
29
+ uniform usampler2D u_countTexture;
30
+ uniform int u_dataWidth;
31
+ uniform float u_cellWidth;
32
+ uniform float u_radiusX;
33
+ uniform float u_invLogMaxCount;
34
+
35
+ ${P}
36
+
37
+ void main() {
38
+ float pixelX = floor(gl_FragCoord.x);
39
+ int level = int(floor(gl_FragCoord.y));
40
+ int startIndex = max(
41
+ 0,
42
+ int(floor((pixelX - u_radiusX) / u_cellWidth)) - 2
43
+ );
44
+ float accumulated = 0.0;
45
+ float coverage = 0.0;
46
+
47
+ for (int offset = 0; offset < 256; offset++) {
48
+ int dataIndex = startIndex + offset;
49
+ if (dataIndex >= u_dataWidth) break;
50
+
51
+ float startCellX = floor(u_cellWidth * float(dataIndex));
52
+ float endCellX = ceil(u_cellWidth * float(dataIndex + 1));
53
+ float centerX = (startCellX + endCellX) * 0.5;
54
+ if (centerX > pixelX + u_radiusX + 1.0) break;
55
+
56
+ float normalizedDistance = (pixelX - centerX) / u_radiusX;
57
+ float weight = gaussianWeight(
58
+ normalizedDistance * normalizedDistance
59
+ );
60
+ if (weight <= 0.0) continue;
61
+
62
+ coverage += weight;
63
+ uint count = texelFetch(
64
+ u_countTexture,
65
+ ivec2(level, dataIndex),
66
+ 0
67
+ ).r;
68
+ if (count > 0u) {
69
+ float intensity = min(log(float(count) + 1.0) * u_invLogMaxCount, 1.0);
70
+ accumulated += intensity * weight;
71
+ }
72
+ }
73
+
74
+ horizontalValue = vec2(accumulated, coverage);
75
+ }
76
+ `,U=`#version 300 es
77
+ precision highp float;
78
+ precision highp int;
79
+
80
+ in vec2 v_texCoord;
81
+ out vec4 fragColor;
82
+
83
+ uniform sampler2D u_horizontalTexture;
84
+ uniform sampler2D u_colorTexture;
85
+ uniform int u_canvasWidth;
86
+ uniform int u_canvasHeight;
87
+ uniform float u_rangeMin;
88
+ uniform float u_rangeMax;
89
+ uniform float u_radiusY;
90
+ uniform float u_intensityGain;
91
+
92
+ ${P}
93
+
94
+ void main() {
95
+ int pixelX = clamp(
96
+ int(floor(gl_FragCoord.x)),
97
+ 0,
98
+ u_canvasWidth - 1
99
+ );
100
+ int pixelY = clamp(
101
+ int(floor((1.0 - v_texCoord.y) * float(u_canvasHeight))),
102
+ 0,
103
+ u_canvasHeight - 1
104
+ );
105
+ float rangeSpan = u_rangeMax - u_rangeMin;
106
+ float heightMinusOne = max(float(u_canvasHeight - 1), 1.0);
107
+ float sourceLevel =
108
+ u_rangeMin + (float(pixelY) / heightMinusOne) * rangeSpan - ${d.LEVEL_MIN}.0;
109
+ float levelRadius = u_radiusY * rangeSpan / heightMinusOne;
110
+ int startLevel = max(0, int(floor(sourceLevel - levelRadius)) - 2);
111
+ float accumulated = 0.0;
112
+ float yCoverage = 0.0;
113
+
114
+ for (int offset = 0; offset < ${d.LEVEL_RANGE}; offset++) {
115
+ int level = startLevel + offset;
116
+ if (level >= ${d.LEVEL_RANGE}) break;
117
+
118
+ float yValue = float(level + ${d.LEVEL_MIN});
119
+ if (yValue < u_rangeMin || yValue > u_rangeMax) continue;
120
+ float centerY =
121
+ ((yValue - u_rangeMin) / rangeSpan) * heightMinusOne;
122
+ if (centerY > float(pixelY) + u_radiusY + 1.0) break;
123
+
124
+ float normalizedDistance = (float(pixelY) - centerY) / u_radiusY;
125
+ float weight = gaussianWeight(
126
+ normalizedDistance * normalizedDistance
127
+ );
128
+ if (weight <= 0.0) continue;
129
+
130
+ vec2 horizontal = texelFetch(
131
+ u_horizontalTexture,
132
+ ivec2(pixelX, level),
133
+ 0
134
+ ).rg;
135
+ accumulated += horizontal.r * weight;
136
+ yCoverage += weight;
137
+ }
138
+
139
+ float xCoverage = texelFetch(
140
+ u_horizontalTexture,
141
+ ivec2(pixelX, 0),
142
+ 0
143
+ ).g;
144
+ float denominator = xCoverage * yCoverage;
145
+ if (denominator <= 0.0) {
146
+ fragColor = vec4(0.0);
147
+ return;
148
+ }
149
+
150
+ // Match Canvas by normalizing against potential sample coverage.
151
+ float intensity = min(
152
+ (accumulated / denominator) * u_intensityGain,
153
+ 1.0
154
+ );
155
+ if (intensity <= 0.001) {
156
+ fragColor = vec4(0.0);
157
+ return;
158
+ }
159
+
160
+ int colorIndex = min(
161
+ int(floor(intensity * float(255))),
162
+ 255
163
+ );
164
+ vec3 color = texelFetch(u_colorTexture, ivec2(colorIndex, 0), 0).rgb;
165
+ fragColor = vec4(color * intensity, intensity);
166
+ }
167
+ `,G=`#version 300 es
168
+ precision highp float;
169
+ precision highp int;
170
+ precision highp usampler2D;
171
+
172
+ in vec2 v_texCoord;
173
+ out vec4 fragColor;
174
+
175
+ uniform usampler2D u_countTexture;
176
+ uniform sampler2D u_colorTexture;
177
+ uniform int u_dataWidth;
178
+ uniform int u_canvasWidth;
179
+ uniform int u_canvasHeight;
180
+ uniform float u_rangeMin;
181
+ uniform float u_rangeMax;
182
+ uniform float u_invLogMaxCount;
183
+
184
+ void main() {
185
+ int pixelX = clamp(
186
+ int(floor(gl_FragCoord.x)),
187
+ 0,
188
+ u_canvasWidth - 1
189
+ );
190
+ int pixelY = clamp(
191
+ int(floor((1.0 - v_texCoord.y) * float(u_canvasHeight))),
192
+ 0,
193
+ u_canvasHeight - 1
194
+ );
195
+ float canvasWidth = float(u_canvasWidth);
196
+ float canvasHeight = float(u_canvasHeight);
197
+ float dataWidth = float(u_dataWidth);
198
+ int blockWidth = max(1, int(floor(canvasWidth / dataWidth)));
199
+ int baseDataIndex = int(floor(float(pixelX) * dataWidth / canvasWidth));
200
+ int dataIndex = -1;
201
+
202
+ for (int candidateOffset = -2; candidateOffset <= 2; candidateOffset++) {
203
+ int candidate = baseDataIndex + candidateOffset;
204
+ if (candidate < 0 || candidate >= u_dataWidth) continue;
205
+ int startX = int(floor(float(candidate) * canvasWidth / dataWidth));
206
+ int endX = min(u_canvasWidth - 1, startX + blockWidth - 1);
207
+ if (pixelX >= startX && pixelX <= endX) {
208
+ dataIndex = candidate;
209
+ break;
210
+ }
211
+ }
212
+
213
+ if (dataIndex < 0) {
214
+ fragColor = vec4(0.0);
215
+ return;
216
+ }
217
+
218
+ float rangeSpan = u_rangeMax - u_rangeMin;
219
+ float heightMinusOne = max(canvasHeight - 1.0, 1.0);
220
+ float yAtPixel =
221
+ u_rangeMin + (float(pixelY) / heightMinusOne) * rangeSpan;
222
+ int baseLevel = int(floor(yAtPixel - ${d.LEVEL_MIN}.0));
223
+ int blockHeight = max(1, int(ceil(canvasHeight / rangeSpan)));
224
+ int level = -1;
225
+
226
+ for (int candidateOffset = -3; candidateOffset <= 3; candidateOffset++) {
227
+ int candidate = baseLevel + candidateOffset;
228
+ if (candidate < 0 || candidate >= ${d.LEVEL_RANGE}) continue;
229
+ float yValue = float(candidate + ${d.LEVEL_MIN});
230
+ if (yValue < u_rangeMin || yValue > u_rangeMax) continue;
231
+ float centerY = ((yValue - u_rangeMin) / rangeSpan) * heightMinusOne;
232
+ int startY = max(0, int(floor(centerY - float(blockHeight) * 0.5)));
233
+ int endY = min(u_canvasHeight - 1, startY + blockHeight - 1);
234
+ if (pixelY >= startY && pixelY <= endY) {
235
+ level = candidate;
236
+ break;
237
+ }
238
+ }
239
+
240
+ if (level < 0) {
241
+ fragColor = vec4(0.0);
242
+ return;
243
+ }
244
+
245
+ uint count = texelFetch(
246
+ u_countTexture,
247
+ ivec2(level, dataIndex),
248
+ 0
249
+ ).r;
250
+ if (count == 0u) {
251
+ fragColor = vec4(0.0);
252
+ return;
253
+ }
254
+
255
+ float intensity = min(log(float(count) + 1.0) * u_invLogMaxCount, 1.0);
256
+ int colorIndex = min(
257
+ int(floor(intensity * float(255))),
258
+ 255
259
+ );
260
+ vec3 color = texelFetch(u_colorTexture, ivec2(colorIndex, 0), 0).rgb;
261
+ fragColor = vec4(color * intensity, intensity);
262
+ }
263
+ `,O=(e,t,a)=>{let r=e.getUniformLocation(t,a);if(!r)throw Error(`Missing WebGL uniform: ${a}`);return r},X=(e,t)=>{let a=e.getAttribLocation(t,"a_position");if(a<0)throw Error("Missing WebGL attribute: a_position");return a};class W extends I{fallbackHandler;disposed=!1;init(e){super.init(e);let{canvas:t,gl:a}=this.state;if(!a)throw Error("WebGL 2.0 is not available");let r=null,i=null,n=null,l=null,s=null,h=null,d=null,c=null;try{if(!a.getExtension("EXT_color_buffer_float"))throw Error("EXT_color_buffer_float is not available");r=this.createProgram({vertex:D,fragment:N}),i=this.createProgram({vertex:D,fragment:U}),n=this.createProgram({vertex:D,fragment:G}),l=this.createQuadBuffer(),s=this.createTexture(),h=this.createTexture(),d=this.createTexture(),c=a.createFramebuffer();let f=a.getParameter(a.MAX_TEXTURE_SIZE);if(!r||!i||!n||!l||!s||!h||!d||!c||!Number.isFinite(f)||f<=0)throw Error("Failed to allocate fluorescence WebGL resources");let g={position:X(a,r),countTexture:O(a,r,"u_countTexture"),dataWidth:O(a,r,"u_dataWidth"),cellWidth:O(a,r,"u_cellWidth"),radiusX:O(a,r,"u_radiusX"),invLogMaxCount:O(a,r,"u_invLogMaxCount")},p={position:X(a,i),horizontalTexture:O(a,i,"u_horizontalTexture"),colorTexture:O(a,i,"u_colorTexture"),canvasWidth:O(a,i,"u_canvasWidth"),canvasHeight:O(a,i,"u_canvasHeight"),rangeMin:O(a,i,"u_rangeMin"),rangeMax:O(a,i,"u_rangeMax"),radiusY:O(a,i,"u_radiusY"),intensityGain:O(a,i,"u_intensityGain")},E={position:X(a,n),countTexture:O(a,n,"u_countTexture"),colorTexture:O(a,n,"u_colorTexture"),dataWidth:O(a,n,"u_dataWidth"),canvasWidth:O(a,n,"u_canvasWidth"),canvasHeight:O(a,n,"u_canvasHeight"),rangeMin:O(a,n,"u_rangeMin"),rangeMax:O(a,n,"u_rangeMax"),invLogMaxCount:O(a,n,"u_invLogMaxCount")};super.updateProps({colors:e.colors&&e.colors.length>0?e.colors:u.FLUORESCENCE_COLORS,data:new Uint32Array(0),display:e.display,fluorescenceMaxCount:e.fluorescenceMaxCount??1,renderMode:e.fluorescenceRenderMode??m.Gaussian,maxTextureSize:f,quadBuffer:l,countTexture:s,colorTexture:h,horizontalTexture:d,horizontalFramebuffer:c,horizontalProgram:r,gaussianProgram:i,gridProgram:n,horizontalLocations:g,gaussianLocations:p,gridLocations:E}),this.updateColorTexture(),this.resize(!1),o(t,this)}catch(e){throw c&&a.deleteFramebuffer(c),d&&a.deleteTexture(d),h&&a.deleteTexture(h),s&&a.deleteTexture(s),l&&a.deleteBuffer(l),n&&a.deleteProgram(n),i&&a.deleteProgram(i),r&&a.deleteProgram(r),t.remove(),e}}setFallbackHandler(e){this.state.canvas.removeEventListener("webglcontextlost",this.handleContextLost),this.fallbackHandler=e,this.state.canvas.addEventListener("webglcontextlost",this.handleContextLost)}handleContextLost=e=>{e.preventDefault(),this.disposed||this.requestFallback(Error("Fluorescence WebGL context lost"))};requestFallback(e){if(this.fallbackHandler){this.fallbackHandler(e);return}throw e instanceof Error?e:Error(String(e))}updateColorTexture(){let{colorTexture:e,colors:t,gl:a}=this.state,r=F(t);a.bindTexture(a.TEXTURE_2D,e),a.texParameteri(a.TEXTURE_2D,a.TEXTURE_MIN_FILTER,a.NEAREST),a.texParameteri(a.TEXTURE_2D,a.TEXTURE_MAG_FILTER,a.NEAREST),a.texImage2D(a.TEXTURE_2D,0,a.RGBA,256,1,0,a.RGBA,a.UNSIGNED_BYTE,r)}rebuildHorizontalTarget(){let{canvas:e,gl:t,horizontalFramebuffer:a,horizontalTexture:r,maxTextureSize:i}=this.state,{width:n}=e;if(n<=0)return;if(n>i||d.LEVEL_RANGE>i)throw Error("Fluorescence WebGL render target exceeds texture limits");t.bindTexture(t.TEXTURE_2D,r),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.NEAREST),t.texImage2D(t.TEXTURE_2D,0,t.RG32F,n,d.LEVEL_RANGE,0,t.RG,t.FLOAT,null),t.bindFramebuffer(t.FRAMEBUFFER,a),t.framebufferTexture2D(t.FRAMEBUFFER,t.COLOR_ATTACHMENT0,t.TEXTURE_2D,r,0);let l=t.checkFramebufferStatus(t.FRAMEBUFFER);if(t.bindFramebuffer(t.FRAMEBUFFER,null),l!==t.FRAMEBUFFER_COMPLETE)throw Error(`Fluorescence WebGL framebuffer incomplete: ${l}`)}uploadCounts(e,t){let{countTexture:a,gl:r,maxTextureSize:i}=this.state;if(t>i)throw Error("Fluorescence data exceeds WebGL texture limits");r.bindTexture(r.TEXTURE_2D,a),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_MIN_FILTER,r.NEAREST),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_MAG_FILTER,r.NEAREST),r.texImage2D(r.TEXTURE_2D,0,r.R32UI,d.LEVEL_RANGE,t,0,r.RED_INTEGER,r.UNSIGNED_INT,e)}bindQuad(e,t){let{gl:a,quadBuffer:r}=this.state;a.useProgram(e),a.bindBuffer(a.ARRAY_BUFFER,r),a.enableVertexAttribArray(t),a.vertexAttribPointer(t,2,a.FLOAT,!1,0,0)}drawGaussian(e,t,a){let{canvas:r,colorTexture:i,countTexture:n,fluorescenceMaxCount:l,gaussianLocations:o,gaussianProgram:s,gl:h,horizontalFramebuffer:u,horizontalLocations:c,horizontalProgram:f,horizontalTexture:g}=this.state,{width:p,height:m}=r,E=y(e,p,m,t,a);if(E.xSampleCount>256)throw Error("Fluorescence Gaussian kernel exceeds shader limits");let T=1/Math.log1p(Math.max(l,1));h.bindFramebuffer(h.FRAMEBUFFER,u),h.viewport(0,0,p,d.LEVEL_RANGE),this.bindQuad(f,c.position),h.activeTexture(h.TEXTURE0),h.bindTexture(h.TEXTURE_2D,n),h.uniform1i(c.countTexture,0),h.uniform1i(c.dataWidth,e),h.uniform1f(c.cellWidth,E.cellWidth),h.uniform1f(c.radiusX,E.radiusX),h.uniform1f(c.invLogMaxCount,T),h.drawArrays(h.TRIANGLES,0,6),h.bindFramebuffer(h.FRAMEBUFFER,null),h.viewport(0,0,p,m),this.bindQuad(s,o.position),h.activeTexture(h.TEXTURE0),h.bindTexture(h.TEXTURE_2D,g),h.uniform1i(o.horizontalTexture,0),h.activeTexture(h.TEXTURE1),h.bindTexture(h.TEXTURE_2D,i),h.uniform1i(o.colorTexture,1),h.uniform1i(o.canvasWidth,p),h.uniform1i(o.canvasHeight,m),h.uniform1f(o.rangeMin,t),h.uniform1f(o.rangeMax,a),h.uniform1f(o.radiusY,E.radiusY),h.uniform1f(o.intensityGain,1.3),h.drawArrays(h.TRIANGLES,0,6)}drawGrid(e,t,a){let{canvas:r,colorTexture:i,countTexture:n,data:l,gl:o,gridLocations:s,gridProgram:h}=this.state,u=1/Math.log1p(S(l,e,t,a));o.bindFramebuffer(o.FRAMEBUFFER,null),o.viewport(0,0,r.width,r.height),this.bindQuad(h,s.position),o.activeTexture(o.TEXTURE0),o.bindTexture(o.TEXTURE_2D,n),o.uniform1i(s.countTexture,0),o.activeTexture(o.TEXTURE1),o.bindTexture(o.TEXTURE_2D,i),o.uniform1i(s.colorTexture,1),o.uniform1i(s.dataWidth,e),o.uniform1i(s.canvasWidth,r.width),o.uniform1i(s.canvasHeight,r.height),o.uniform1f(s.rangeMin,t),o.uniform1f(s.rangeMax,a),o.uniform1f(s.invLogMaxCount,u),o.drawArrays(o.TRIANGLES,0,6)}updateProps(e){let t=void 0!==e.colors,a=e.data,r=a?a.length/d.LEVEL_RANGE:0,i=void 0===a||0===a.length||Number.isInteger(r)&&r>0,n=void 0!==e.range||void 0!==e.renderMode||void 0!==e.display||t,{data:l,...o}=e;super.updateProps({...o,...i&&void 0!==a?{data:a}:{}});try{a&&i&&this.uploadCounts(a,r),t&&this.updateColorTexture(),n&&this.state.data.length>0&&this.draw()}catch(e){this.requestFallback(e)}}setRange(e){e&&this.updateProps({range:e})}setRenderMode(e){this.updateProps({renderMode:e})}clear(){let{canvas:e,gl:t}=this.state;this.state.data=new Uint32Array(0),t.bindFramebuffer(t.FRAMEBUFFER,null),t.viewport(0,0,e.width,e.height),this.clearRect()}render(e){let t=e?.fluorescenceData;if(!t||0===t.length){this.clear();return}let a=t.length/d.LEVEL_RANGE;if(Number.isInteger(a)&&!(a<=0))try{this.state.data=t,this.state.fluorescenceMaxCount=e.fluorescenceMaxCount,this.uploadCounts(t,a),this.draw()}catch(e){this.requestFallback(e)}}draw(){let{canvas:e,data:t,display:a,gl:r,range:i=u.RANGE,renderMode:n}=this.state;if(!a||0===t.length||0===e.width||0===e.height)return;let l=t.length/d.LEVEL_RANGE;if(!Number.isInteger(l)||l<=0)return;let[o,s]=i;if(!(s<=o))try{r.bindFramebuffer(r.FRAMEBUFFER,null),r.viewport(0,0,e.width,e.height),r.clearColor(0,0,0,0),r.clear(r.COLOR_BUFFER_BIT),n===m.Grid?this.drawGrid(l,o,s):this.drawGaussian(l,o,s)}catch(e){this.requestFallback(e)}}resize(e=!0){try{if(super.resize(!1),!this.state.horizontalTexture)return;this.rebuildHorizontalTarget(),e&&this.draw()}catch(e){this.requestFallback(e)}}toDataURL(e="image/png",t){this.draw();let{canvas:a}=this.state,r=document.createElement("canvas");r.width=a.width,r.height=a.height;let i=r.getContext("2d");return i?(i.translate(0,a.height),i.scale(1,-1),i.drawImage(a,0,0),r.toDataURL(e,t)):null}dispose(){if(this.disposed)return;this.disposed=!0;let{canvas:e,colorTexture:t,countTexture:a,gaussianProgram:r,gl:i,gridProgram:n,horizontalFramebuffer:l,horizontalProgram:o,horizontalTexture:h,quadBuffer:u}=this.state;s(e,this),e?.removeEventListener("webglcontextlost",this.handleContextLost),i&&(i.deleteProgram(o),i.deleteProgram(r),i.deleteProgram(n),i.deleteBuffer(u),i.deleteTexture(a),i.deleteTexture(t),i.deleteTexture(h),i.deleteFramebuffer(l)),this.fallbackHandler=void 0,super.dispose()}}class H{state;constructor(e){this.state={id:e.id,container:void 0,canvas:null,ctx:null,range:e.range??[-20,100]},this.init(e)}updateProps(e){this.state={...this.state,...e}}init(e){let{id:t}=this.state,a=document.getElementById(t),r=document.createElement("canvas"),i=r.getContext?.("2d");r.style.transform="scaleY(-1)",i&&(i.imageSmoothingEnabled=!1,i.lineJoin="miter",i.lineCap="butt",i.textBaseline="middle"),i&&this.updateProps({container:a??void 0,canvas:r,ctx:i}),a?.appendChild(r),this.resize(!1)}clearRect(){let{ctx:e,canvas:t}=this.state;e.clearRect(0,0,t.width,t.height)}resize(e=!0){let{canvas:t,container:a}=this.state,{clientWidth:r,clientHeight:i}=a;r&&i&&(t.width=r,t.height=i,e&&setTimeout(()=>{this.draw()}))}draw(){}dispose(){let{canvas:e,container:t}=this.state;t?.removeChild(e)}}let B=256,z=new Float32Array(256);for(let e=0;e<256;e++){let t=e/255;z[e]=Math.max(0,(Math.exp(-4.5*t)-L)*A)}class V extends H{init(e){super.init(e);let{colors:t,display:a,fluorescenceRenderMode:r}=e;this.updateProps({colors:t,data:new Uint32Array(0),display:a,renderMode:r??m.Gaussian}),this.resize()}updateProps(e){void 0!==e.colors&&(this.colorLookupCache=null);let t=e.range&&(e.range[0]!==this.state.range?.[0]||e.range[1]!==this.state.range?.[1]),a=void 0!==e.renderMode&&e.renderMode!==this.state.renderMode;super.updateProps(e),(t||a)&&this.state.data?.length>0&&this.draw()}setRenderMode(e){this.updateProps({renderMode:e})}clearImageData(){let{ctx:e,canvas:{height:t,width:a}}=this.state;t&&a&&this.updateProps({imageData:e.createImageData(a,t)})}clearRect(){super.clearRect(),this.clearImageData()}clear(){this.clearRect(),this.updateProps({data:new Uint32Array(0)})}dispose(){this.intensityMatrixCache=null,this.gaussianGeometryCache=null,this.colorLookupCache=null,this.colorCache.clear(),this.blockPositionsCache=null,this.lastBlockRenderParams=null,super.dispose()}drawBlocks(){let{imageData:e,data:t,canvas:a,ctx:r,colors:i=u.FLUORESCENCE_COLORS,range:n=u.RANGE}=this.state;if(!t||0===t.length||!e)return;let{width:l,height:o}=a,[s,h]=n,c=h-s;if(c<=0)return;let f=t.length/d.LEVEL_RANGE;if(!Number.isInteger(f)||f<=0)return;let g={dataLength:f,width:l,height:o,rangeMin:s,rangeMax:h};if(!this.lastBlockRenderParams||Object.keys(g).some(e=>this.lastBlockRenderParams?.[e]!==g[e])||!this.blockPositionsCache||this.blockPositionsCache.length!==f){this.blockPositionsCache=Array(f);let e=Math.max(1,Math.floor(l/f));for(let t=0;t<f;t++){let a=Math.floor(t*l/f),r=Math.min(l-1,a+e-1);this.blockPositionsCache[t]={startX:a,endX:r,width:r-a+1}}}this.lastBlockRenderParams=g;let p=e.data;p.fill(0);let m=1/c,E=o-1,T=Math.max(1,Math.ceil(o/c)),_=0;for(let e=0;e<f;e++){let a=e*d.LEVEL_RANGE;for(let e=0;e<d.LEVEL_RANGE;e++){let r=t[a+e];if(r<=0)continue;let i=e+d.LEVEL_MIN;i<s||i>h||!(r>_)||(_=r)}}let x=Math.log(Math.max(_,1)+1);for(let e=0;e<f;e++){let{startX:a,endX:r}=this.blockPositionsCache[e],n=e*d.LEVEL_RANGE;for(let e=0;e<d.LEVEL_RANGE;e++){let o=t[n+e];if(o<=0)continue;let u=e+d.LEVEL_MIN;if(u<s||u>h)continue;let c=Math.max(0,Math.floor((u-s)*m*E-T/2)),f=Math.min(E,c+T-1),g=Math.log(o+1)/x,_=Math.round(255*g),{r:M,g:C,b:R}=this.interpolateColor(i,g);for(let e=c;e<=f;e++){let t=e*l;for(let e=a;e<=r;e++){let a=(t+e)*4;p[a]=M,p[a+1]=C,p[a+2]=R,p[a+3]=_}}}}r.putImageData(e,0,0)}resize(){super.resize(),this.clearImageData()}setRange(e){e&&this.updateProps({range:e})}render(e){if(e?.fluorescenceData?.length>=0){if(0===e.fluorescenceData.length){this.state.fluorescenceMaxCount=e.fluorescenceMaxCount,this.clear();return}(this.state.data?.length??0)!==e.fluorescenceData.length&&(this.clearImageData(),this.intensityMatrixCache=null,this.gaussianGeometryCache=null,this.blockPositionsCache=null,this.lastBlockRenderParams=null),this.state.data=e.fluorescenceData,this.state.fluorescenceMaxCount=e.fluorescenceMaxCount,this.draw()}}interpolateColor(e,t){if(!e||0===e.length)return{r:255,g:255,b:255,a:255};t=Math.max(0,Math.min(1,t));let a=e=>"string"==typeof e?this.hexToRgb(e):{r:e.r,g:e.g,b:e.b};if(1===e.length||0===t)return{...a(e[0]),a:255};if(1===t)return{...a(e[e.length-1]),a:255};let r=t*(e.length-1),i=Math.floor(r),n=r-i,l=Math.min(i,e.length-2),o=a(e[l]),s=a(e[l+1]);return{r:Math.round(o.r+(s.r-o.r)*n),g:Math.round(o.g+(s.g-o.g)*n),b:Math.round(o.b+(s.b-o.b)*n),a:255}}colorCache=new Map;hexToRgb(e){let t=this.colorCache.get(e);if(t)return t;let a=r(e),i={r:a.r,g:a.g,b:a.b};return this.colorCache.set(e,i),i}intensityMatrixCache=null;gaussianGeometryCache=null;colorLookupCache=null;blockPositionsCache=null;lastBlockRenderParams=null;prepareGaussianGeometry(e,t,a,r,i){let n=this.gaussianGeometryCache;if(n&&n.dataLength===e&&n.width===t&&n.height===a&&n.rangeMin===r&&n.rangeMax===i)return n;let{cellWidth:l,radiusX:o,radiusY:s,rangeSpan:h}=y(e,t,a,r,i),u=2*o+2,c=2*s+2,f=new Int32Array(e),g=new Uint8Array(e),p=new Float32Array(e*u),m=new Float32Array(t),E=new Int32Array(d.LEVEL_RANGE),T=new Uint8Array(d.LEVEL_RANGE),_=new Float32Array(d.LEVEL_RANGE*c),x=new Float32Array(a);for(let a=0;a<e;a++){let e=(Math.floor(l*a)+Math.ceil(l*(a+1)))/2,r=Math.max(0,Math.floor(e-o)),i=Math.min(t-1,Math.ceil(e+o))-r+1,n=a*u;f[a]=r,g[a]=i;for(let t=0;t<i;t++){let a=r+t,i=(a-e)/o,l=i*i;if(l>=1)continue;let s=z[Math.floor(255*l)];p[n+t]=s,m[a]+=s}}for(let e=0;e<d.LEVEL_RANGE;e++){let t=e+d.LEVEL_MIN;if(t<r||t>i)continue;let n=(t-r)/h*(a-1),l=Math.max(0,Math.floor(n-s)),o=Math.min(a-1,Math.ceil(n+s))-l+1,u=e*c;E[e]=l,T[e]=o;for(let e=0;e<o;e++){let t=l+e,a=(t-n)/s,r=a*a;if(r>=1)continue;let i=z[Math.floor(255*r)];_[u+e]=i,x[t]+=i}}let M={dataLength:e,height:a,rangeMax:i,rangeMin:r,width:t,xCoverage:m,xLengths:g,xStarts:f,xStride:u,xWeights:p,yCoverage:x,yLengths:T,yStarts:E,yStride:c,yWeights:_};return this.gaussianGeometryCache=M,M}draw(){let{imageData:e,data:t,canvas:a,ctx:r,colors:i=u.FLUORESCENCE_COLORS,range:n=u.RANGE,fluorescenceMaxCount:l=1,display:o,renderMode:s=m.Gaussian}=this.state;if(!o||!t||0===t.length||!e)return;if(s===m.Grid){this.drawBlocks();return}let{width:h,height:c}=a,[f,g]=n;if(g-f<=0)return;let p=t.length/d.LEVEL_RANGE;if(!Number.isInteger(p)||p<=0)return;let E=!this.colorLookupCache,T=e.data;T.fill(0);let _=1/Math.log1p(Math.max(l,1)),x=h*c;this.intensityMatrixCache&&this.intensityMatrixCache.length===x?this.intensityMatrixCache.fill(0):this.intensityMatrixCache=new Float32Array(x);let M=this.intensityMatrixCache,{xCoverage:C,xLengths:R,xStarts:b,xStride:L,xWeights:v,yCoverage:A,yLengths:y,yStarts:w,yStride:F,yWeights:S}=this.prepareGaussianGeometry(p,h,c,f,g);for(let e=0;e<p;e++){let a=e*d.LEVEL_RANGE,r=b[e],i=R[e],n=e*L;for(let e=0;e<d.LEVEL_RANGE;e++){let l=t[a+e];if(l<=0)continue;let o=y[e];if(0===o)continue;let s=Math.min(Math.log1p(l)*_,1),u=w[e],d=e*F;for(let e=0;e<o;e++){let t=S[d+e];if(0===t)continue;let a=(u+e)*h,l=s*t;for(let e=0;e<i;e++){let t=v[n+e];if(0===t)continue;let i=a+r+e;M[i]+=l*t}}}}if(E||!this.colorLookupCache||256!==this.colorLookupCache.length){this.colorLookupCache=Array(256);for(let e=0;e<256;e++){let t=e/255;this.colorLookupCache[e]=this.interpolateColor(i,t)}}let I=this.colorLookupCache;for(let e=0;e<c;e++){let t=A[e];if(t<=0)continue;let a=e*h;for(let e=0;e<h;e++){let r=a+e,i=M[r];if(i<=0)continue;let n=C[e]*t;if(n<=0)continue;let l=Math.min(i/n*1.3,1);if(l<=.001)continue;let o=I[Math.min(Math.floor(255*l),255)],s=4*r;T[s]=o.r,T[s+1]=o.g,T[s+2]=o.b,T[s+3]=Math.round(255*l)}}r.putImageData(e,0,0)}}class Y{backend;latestData=null;props;fallingBack=!1;disposed=!1;constructor(e){this.init(e)}get state(){return this.backend.state}init(e){this.backend?.dispose(),this.props={...e},this.latestData=null,this.fallingBack=!1,this.disposed=!1,this.backend=this.createBackend(e)}createBackend(e){if((e.renderer??p.WebGL)===p.WebGL){if(C())try{let t=new W(e);return t.setFallbackHandler(e=>this.fallbackToCanvas(e)),t}catch(e){console.warn("[Fluorescence] WebGL initialization failed, falling back to Canvas",e)}else console.warn("[Fluorescence] WebGL 2.0 not supported, falling back to Canvas")}return new V(e)}fallbackToCanvas(e){if(!this.disposed&&!this.fallingBack&&!(this.backend instanceof V)){this.fallingBack=!0;try{let{colors:t,display:a,range:r,renderMode:i}=this.backend.state;console.warn("[Fluorescence] WebGL rendering failed, falling back to Canvas",e),this.backend.dispose(),this.props={...this.props,colors:t,display:a,fluorescenceRenderMode:i,range:r,renderer:p.Canvas},this.backend=new V(this.props),this.latestData&&this.backend.render(this.latestData)}finally{this.fallingBack=!1}}}updateProps(e){void 0!==e.colors&&(this.props.colors=e.colors),void 0!==e.display&&(this.props.display=e.display),void 0!==e.range&&(this.props.range=e.range),void 0!==e.renderMode&&(this.props.fluorescenceRenderMode=e.renderMode);let t=e.data??this.backend.state.data;(void 0!==e.data||void 0!==e.fluorescenceMaxCount)&&(this.latestData=0===t.length?null:{fluorescenceData:t,fluorescenceMaxCount:e.fluorescenceMaxCount??this.backend.state.fluorescenceMaxCount}),this.backend.updateProps(e)}render(e){this.latestData=e,this.backend.render(e)}setRange(e){this.props.range=e,this.backend.setRange(e)}setRenderMode(e){this.props.fluorescenceRenderMode=e,this.backend.setRenderMode(e)}draw(){this.backend.draw()}clearImageData(){this.backend instanceof V&&this.backend.clearImageData()}clearRect(){this.backend.clearRect()}clear(){this.latestData=null,this.backend.clear()}resize(){this.backend.resize()}interpolateColor(e,t){return{...w(e,t),a:255}}hexToRgb(e){let{r:t,g:a,b:i}=r(e);return{r:t,g:a,b:i}}dispose(){this.disposed||(this.disposed=!0,this.latestData=null,this.backend.dispose())}}let $=e=>new Y(e);class q extends H{init(e){super.init(e),this.state.canvas.style.transform="scaleY(1)";let t=e.fillStyle??u.FILL_STYLE,a=e.fillStylePrimary??u.FILL_STYLE_PRIMARY,r=e.fillStyleTransparentBase??u.FILL_STYLE_TRANSPARENT_BASE;this.updateProps({fillStyle:t,fillStylePrimary:a,fillStyleTransparentBase:r,baseWidth:6,padding:e.padding??0,step:e.ticksStep??10,lineWidth:1,data:{value:0,limit:0}})}clear(){this.clearRect();let{ctx:e,BGCanvas:t}=this.state;t&&e.drawImage(t,0,0)}resize(){super.resize(),this.state.lineWidth&&this.drawBackground()}render(e){this.state.data={...this.state.data,...e},this.draw()}draw(){let{data:{value:e},range:[t,a],canvas:r,ctx:i,baseWidth:n,padding:l,BGCanvas:o,fillStylePrimary:s}=this.state;this.clearRect(),o&&i.drawImage(o,0,0);let h=3*n,u=r.height-2*l,d=(r.width-h)/2,c=(e-t)/(a-t)*u;i.fillStyle=s,i.fillRect(d,u-c+l,h,c),this.drawLimit()}drawLimit(){let{data:{limit:e},range:[t,a],canvas:r,ctx:i,baseWidth:n,padding:l,lineWidth:o,fillStylePrimary:s}=this.state;if(!_(e))return;let h=3*n,u=r.height-2*l,d=(r.width-h)/2,c=u*(a-e)/(a-t)+l;i.beginPath(),i.moveTo(d+h,c),i.lineTo(d+h+9,c-9),i.lineTo(d+h+9+30,c-9),i.lineTo(d+h+9+30,c+9),i.lineTo(d+h+9,c+9),i.lineTo(d+h,c),i.lineTo(d,c),i.lineWidth=o,i.strokeStyle=s,i.stroke(),i.fillStyle=s,i.font="12px Arial",i.textAlign="center",i.textBaseline="middle",i.fillText(e.toString(),d+h+9+15,c+1)}drawBackground(){let{canvas:e,baseWidth:t,padding:a,lineWidth:r,step:i,range:[n,l],fillStyle:o,fillStyleTransparentBase:s}=this.state,h=document.createElement("canvas");h.width=e.width,h.height=e.height;let u=h.getContext("2d");if(!u)return;let d=3*t,c=e.height-2*a,f=(e.width-d)/2,g=Array.from({length:11},(e,t)=>n+Math.round(t*(l-n)/10/i)*i);u.fillStyle=s,u.fillRect(f,a,d,c);for(let e=n;e<=l;e+=i){let i=g.includes(e),s=c*(l-e)/(l-n)+a;u.beginPath(),u.moveTo(f-(i?t:t/2),s),u.lineTo(f,s),u.strokeStyle=o,u.lineWidth=i?r:r/2,u.stroke(),i&&(u.fillStyle=o,u.font="12px Arial",u.textAlign="right",u.textBaseline="middle",u.fillText(e.toString(),f-1.2*t,s))}this.state.BGCanvas=h}}let Q=`#version 300 es
2
264
  in vec2 a_position;
3
265
  out vec2 v_texCoord;
4
266
 
@@ -7,7 +269,7 @@ void main() {
7
269
  // 将 [-1, 1] 映射到 [0, 1]
8
270
  v_texCoord = a_position * 0.5 + 0.5;
9
271
  }
10
- `,_=Number.NEGATIVE_INFINITY,I=`#version 300 es
272
+ `,Z=Number.NEGATIVE_INFINITY,J=`#version 300 es
11
273
  precision highp float;
12
274
 
13
275
  in vec2 v_texCoord;
@@ -17,32 +279,46 @@ uniform sampler2D u_dataTexture;
17
279
  uniform sampler2D u_colorTexture;
18
280
  uniform float u_rangeMin;
19
281
  uniform float u_rangeMax;
282
+ uniform int u_ringHead;
283
+ uniform int u_ringValidCount;
284
+ uniform int u_ringCapacity;
285
+ uniform int u_dataHeight;
286
+ uniform int u_tileWidth;
287
+ uniform bool u_ringMode;
20
288
 
21
- // 特殊标记值,用于表示无效数据(NaN、空行等)
22
- // -1.0e38 是 float32 可精确表示的足够小的负值,远小于真实频谱电平,
23
- // 且与 JS 端上传的 -Infinity 比较恒为 true(-Infinity <= -1.0e38)
289
+ // 该阈值远小于有效频谱电平,也能覆盖脚本侧上传的负无穷。
24
290
  const float INVALID_VALUE = -1.0e38;
25
291
 
26
292
  void main() {
27
- // Y 轴翻转:从底部向上绘制(与 Canvas 版本一致)
293
+ // 纵向翻转与二维画布的坐标方向保持一致。
28
294
  vec2 flippedCoord = vec2(v_texCoord.x, 1.0 - v_texCoord.y);
29
295
 
30
- // 采样数据纹理获取原始值
31
- float value = texture(u_dataTexture, flippedCoord).r;
296
+ float value;
297
+ if (u_ringMode) {
298
+ // 头部空行保持透明,有效逻辑行再映射到环形纹理的物理行。
299
+ int logicalRow = min(int(floor(flippedCoord.y * float(u_dataHeight))), u_dataHeight - 1);
300
+ int firstLogicalRow = u_dataHeight - u_ringValidCount;
301
+ if (logicalRow < firstLogicalRow || u_ringCapacity <= 0) {
302
+ fragColor = vec4(0.0, 0.0, 0.0, 0.0);
303
+ return;
304
+ }
305
+
306
+ int physicalRow = (u_ringHead + logicalRow - firstLogicalRow) % u_ringCapacity;
307
+ int localColumn = min(int(floor(flippedCoord.x * float(u_tileWidth))), u_tileWidth - 1);
308
+ value = texelFetch(u_dataTexture, ivec2(localColumn, physicalRow), 0).r;
309
+ } else {
310
+ value = texture(u_dataTexture, flippedCoord).r;
311
+ }
32
312
 
33
- // 检查是否为无效值(特殊标记或 NaN)
34
- // 注意:GLSL 中 NaN != NaN,所以 value != value 可以检测 NaN
313
+ // 非数值与无效标记统一保持透明。
35
314
  if (value <= INVALID_VALUE || value != value) {
36
- // 无效数据显示为透明
37
315
  fragColor = vec4(0.0, 0.0, 0.0, 0.0);
38
316
  return;
39
317
  }
40
318
 
41
- // 归一化到 [0, 1]
319
+ // 将有效值归一化后查询颜色纹理。
42
320
  float normalized = (value - u_rangeMin) / (u_rangeMax - u_rangeMin);
43
321
  normalized = clamp(normalized, 0.0, 1.0);
44
-
45
- // 从颜色查找纹理获取颜色
46
322
  fragColor = texture(u_colorTexture, vec2(normalized, 0.5));
47
323
  }
48
- `;class P extends A{init(t){super.init(t);let{gl:e}=this.state;if(!e)return;this.state.canvas.__rfkitHeatmap=this;let a=t.colors,r=a&&a.length>0?a:o.FLUORESCENCE_COLORS,i=this.createProgram({vertex:S,fragment:I}),l=this.createQuadBuffer(),n=this.createTexture(),h=this.createTexture(),u=e.getParameter(e.MAX_TEXTURE_SIZE);if(n){e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,n);let t=new Float32Array([_]);e.texImage2D(e.TEXTURE_2D,0,e.R32F,1,1,0,e.RED,e.FLOAT,t)}let[c,d]=t.range??o.RANGE,f=new s(r,c,d,.1);this.updateProps({data:[],colors:r,program:i,quadBuffer:l,dataTexture:n,dataTextures:n?[n]:[],dataTiles:[],colorTexture:h,dataWidth:0,dataHeight:0,CI:f,maxTextureSize:u}),e.enable(e.BLEND),e.blendFunc(e.SRC_ALPHA,e.ONE_MINUS_SRC_ALPHA),this.updateColorTexture(),e.clearColor(0,0,0,0),e.clear(e.COLOR_BUFFER_BIT),this.resize()}updateColorTexture(){let{gl:t,colorTexture:e,CI:a,range:r}=this.state;if(!t||!e||!a)return;let[i,l]=r,s=new Uint8Array(1024);for(let t=0;t<256;t++){let e=i+t/255*(l-i),r=a.getColor(e);s[4*t]=r.r,s[4*t+1]=r.g,s[4*t+2]=r.b,s[4*t+3]=r.a}t.activeTexture(t.TEXTURE1),t.bindTexture(t.TEXTURE_2D,e),t.texImage2D(t.TEXTURE_2D,0,t.RGBA,256,1,0,t.RGBA,t.UNSIGNED_BYTE,s)}updateDataTexture(){let{gl:t,data:e}=this.state;if(!t||0===e.length)return;let a=e.length,r=0;for(let t=0;t<a;t++){let a=e[t]?.length??0;a>r&&(r=a)}if(0===r)return;let i=this.state.maxTextureSize||t.getParameter(t.MAX_TEXTURE_SIZE);if(!i||i<=0)return;let l=Math.ceil(r/i),s=Math.ceil(a/i),n=l*s,o=this.state.dataTextures??[];if(o.length<n){let t=n-o.length;for(let e=0;e<t;e++){let t=this.createTexture();t&&o.push(t)}}else if(o.length>n){for(let e=n;e<o.length;e++)t.deleteTexture(o[e]);o=o.slice(0,n)}let h=[];t.activeTexture(t.TEXTURE0);for(let n=0;n<s;n++){let s=n*i,u=Math.min(i,a-s);for(let a=0;a<l;a++){let c=a*i,d=Math.min(i,r-c),f=n*l+a,g=o[f];if(!g)continue;let p=new Float32Array(d*u);for(let t=0;t<u;t++){let a=e[s+t],r=a?.length??0,i=t*d;for(let t=0;t<d;t++){let e=c+t;if(0===r||e>=r){p[i+t]=_;continue}let l=a[e];void 0===l||Number.isNaN(l)?p[i+t]=_:p[i+t]=l}}t.bindTexture(t.TEXTURE_2D,g),t.texImage2D(t.TEXTURE_2D,0,t.R32F,d,u,0,t.RED,t.FLOAT,p),h[f]={startCol:c,startRow:s,width:d,height:u}}}this.updateProps({dataTexture:o[0]??null,dataTextures:o,dataTiles:h,dataWidth:r,dataHeight:a})}updateProps(t){super.updateProps(t);let e=t.colors??this.state.colors,a=t.range??this.state.range;if((t.colors||t.range)&&e.length>0){let[t,r]=a;this.state.CI?this.state.CI.setColors(e,t,r,.1):this.state.CI=new s(e,t,r,.1),this.updateColorTexture()}}setRange(t){t&&(this.updateProps({range:t}),this.draw())}clear(){this.clearRect(),this.updateProps({data:[]})}render(t){t?.length>0&&(this.state.data=t,this.updateDataTexture(),this.draw())}draw(){let{gl:t,program:e,quadBuffer:a,dataTextures:r,dataTiles:i,colorTexture:l,data:s,range:n,dataWidth:o,dataHeight:h,canvas:u}=this.state;if(!t||!e||!a||!l||(t.clearColor(0,0,0,0),t.clear(t.COLOR_BUFFER_BIT),0===s.length||0===o||0===h||0===i.length||0===r.length))return;let[c,d]=n;t.useProgram(e),t.bindBuffer(t.ARRAY_BUFFER,a);let f=t.getAttribLocation(e,"a_position");t.enableVertexAttribArray(f),t.vertexAttribPointer(f,2,t.FLOAT,!1,0,0);let g=t.getUniformLocation(e,"u_dataTexture"),p=t.getUniformLocation(e,"u_colorTexture"),m=t.getUniformLocation(e,"u_rangeMin"),T=t.getUniformLocation(e,"u_rangeMax");if(!g||!p||!m||!T)return;t.activeTexture(t.TEXTURE1),t.bindTexture(t.TEXTURE_2D,l),t.uniform1i(p,1),t.uniform1f(m,c),t.uniform1f(T,d);let C=u.width,E=u.height,M=t=>Math.round(t/o*C),R=t=>Math.round(t/h*E);for(let e=0;e<i.length;e++){let a=i[e],l=r[e];if(!a||!l)continue;let s=M(a.startCol),n=M(a.startCol+a.width),o=R(a.startRow),h=E-R(a.startRow+a.height),u=n-s,c=E-o-h;u<=0||c<=0||(t.viewport(s,h,u,c),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,l),t.uniform1i(g,0),t.drawArrays(t.TRIANGLES,0,6))}t.viewport(0,0,C,E)}resize(){super.resize()}toDataURL(t="image/png",e){this.draw();let{canvas:a}=this.state,r=document.createElement("canvas");r.width=a.width,r.height=a.height;let i=r.getContext("2d");return i?(i.translate(0,a.height),i.scale(1,-1),i.drawImage(a,0,0),r.toDataURL(t,e)):null}dispose(){let{gl:t,program:e,quadBuffer:a,dataTextures:r,colorTexture:i}=this.state;if(t){for(let i of(e&&t.deleteProgram(e),a&&t.deleteBuffer(a),r))t.deleteTexture(i);i&&t.deleteTexture(i)}super.dispose()}}class x extends n{init(t){super.init(t);let{colors:e}=t;this.updateProps({colors:e,data:[]}),this.resize()}updateProps(t){super.updateProps(t);let{range:e=this.state.range,colors:a=this.state.colors}=t;if(e&&a){let[t,r]=e;C(t)&&C(r)&&t<r&&(this.state.CI?this.state.CI.setColors(a,t,r,.1):this.updateProps({CI:new s(a,t,r,.1)}))}}clearImageData(){let{ctx:t,canvas:{height:e,width:a}}=this.state;e&&a&&this.updateProps({imageData:t.createImageData(a,e)})}clearRect(){super.clearRect(),this.clearImageData()}clear(){this.clearRect(),this.updateProps({data:[]})}resize(){super.resize(),this.clearImageData()}setRange(t){t&&(this.updateProps({range:t}),this.draw())}render(t){t?.length>=0&&(this.state.data=t,this.draw())}draw(){let{imageData:t,data:e,canvas:a,ctx:r,CI:i}=this.state;0!==e.length&&(m(a.width,a.height,e,t.data,t=>i.getColor(t)),r.putImageData(t,0,0))}}function b(t){let{renderer:e=f.WebGL}=t;if(e===f.WebGL){if(R())return new P(t);console.warn("[Heatmap] WebGL 2.0 not supported, falling back to Canvas")}return new x(t)}let w=function(t){return b(t)},y=w,N=!1;class v extends n{init(t){super.init(t);let{series:e,barValue2Color:a,disabledClearRect:r,colors:i}=t;this.updateProps({interval:0,series:{},barValue2Color:a,disabledClearRect:r,colors:i}),this.setRange(this.state.range),Array.isArray(e)&&e.forEach(t=>{this.setSeries(t)})}updateProps(t){super.updateProps(t);let{colors:e=this.state.colors}=t;this.state.barValue2Color&&!this.state.CI&&e&&this.updateProps({CI:new s(e,0,100,.1)})}clear(){super.clearRect();let{series:t}=this.state;Object.entries(t).forEach(([t,e])=>{this.setSeries({...e,name:t,data:void 0})})}setRange(t){this.updateProps({range:[t[0],t[1],t[1]-t[0]]}),this.draw()}setIntervel(t){t&&this.updateProps({interval:t})}setSeries(t){if(t?.name){let{name:e}=t,{series:a}=this.state;a[e]={thickness:o.THICKNESS,display:!0,color:o.COLOR,type:d.Line,data:void 0,path:void 0,orientation:c.Vertical,...a[e],...t},this.draw()}}render(t){t&&this.setSeries(t),this.draw()}draw(){let{series:t,ctx:a,disabledClearRect:r}=this.state;r||this.clearRect();let i=Object.values(t),{range:l,canvas:s}=this.state,n=l[0],h=l[1],{width:u,height:f}=s,g=h-n;a.save(),a.translate(.5,.5);for(let t=0;t<i.length;t+=1){let r=i[t],{display:l,type:s,data:h,orientation:p}=r,m=r.thickness??o.THICKNESS,T=r.color??o.COLOR;if(!h||!l)continue;let C=h.length,E=u/C;switch(s){case d.Line:case d.Stepline:a.lineWidth=m,a.strokeStyle=T;break;case d.Circle:case d.Rect:case d.Bar:case d.Area:a.fillStyle=T}if(s===d.Line){let t=[];for(let e=0;e<C;e+=1){let r=h[e];if(void 0===r||Number.isNaN(r)){if(t.length>0){a.beginPath(),a.moveTo(t[0][0],t[0][1]);for(let e=1;e<t.length;e++)a.lineTo(t[e][0],t[e][1]);a.lineWidth=m,a.strokeStyle=T,a.stroke(),t=[]}}else{let a=Math.round((e+.5)*E),i=Math.round((r-n)/g*f);t.push([a,i])}}if(t.length>0){a.beginPath(),a.moveTo(t[0][0],t[0][1]);for(let e=1;e<t.length;e++)a.lineTo(t[e][0],t[e][1]);a.lineWidth=m,a.strokeStyle=T,a.stroke()}}if(s===d.Stepline){let t=[];for(let e=0;e<C;e+=1){let r=h[e];if(void 0===r||Number.isNaN(r)){if(t.length>0){a.beginPath(),a.moveTo(t[0][0],t[0][1]);for(let e=1;e<t.length;e++){let[r,i]=t[e-1],[l,s]=t[e];a.lineTo(l,i),a.lineTo(l,s)}let[e,r]=t[t.length-1],i=Math.round(e+E);a.lineTo(i,r),a.lineWidth=m,a.strokeStyle=T,a.stroke(),t=[]}}else{let a=Math.round(e*E),i=Math.round((r-n)/g*f);t.push([a,i])}}if(t.length>0){a.beginPath(),a.moveTo(t[0][0],t[0][1]);for(let e=1;e<t.length;e++){let[r,i]=t[e-1],[l,s]=t[e];a.lineTo(l,i),a.lineTo(l,s)}let[e,r]=t[t.length-1],i=Math.round(e+E);a.lineTo(i,r),a.lineWidth=m,a.strokeStyle=T,a.stroke()}}if(s===d.Circle){let t=+m/2,e=2*Math.PI;for(let r=0;r<C;r+=1){let i=Math.round((r+.5)*E),l=Math.round((h[r]-n)/g*f);a.beginPath(),a.arc(i,l,t,0,e),a.fillStyle=T,a.fill()}}if(s===d.Rect){let t=+m;for(let e=0;e<C;e+=1){let r=Math.round((e+.5)*E),i=Math.round((h[e]-n)/g*f);a.beginPath(),a.rect(r,i,t,t),a.fillStyle=T,a.fill()}}if(s===d.Area){let t=[],{r,g:i,b:l}=e(T??o.COLOR),s=t=>{if(0===t.length)return;let[e,s]=t[0],[n,o]=t[t.length-1],h=Math.round(n+E);a.beginPath(),a.moveTo(e,0),a.lineTo(e,s);for(let e=1;e<t.length;e++){let[r,i]=t[e-1],[l,s]=t[e];a.lineTo(l,i),a.lineTo(l,s)}a.lineTo(h,o),a.lineTo(h,0),a.closePath(),a.fillStyle=`rgba(${r}, ${i}, ${l}, 1)`,a.fill()};for(let e=0;e<C;e+=1){let a=h[e];void 0===a||Number.isNaN(a)?(s(t),t=[]):t.push([Math.round(e*E),Math.round((a-n)/g*f)])}s(t)}if(s===d.Bar){if(p===c.Horizontal){let t=f/C,e=Math.round(t);for(let r=0;r<C;r+=1){let i=Math.round(f-(r+1)*t),l=Math.round((h[r]-n)/g*u),s=u-l;a.beginPath(),a.rect(s,i,l,e),a.fillStyle=T,a.fill()}}else for(let t=0;t<C;t+=1){let e=Math.floor(t*u/C),r=Math.floor((t+1)*u/C)-e,i=Math.round((h[t]-n)/g*f);a.beginPath(),a.rect(e,0,r,i),a.fillStyle=this.state.barValue2Color?this.state.CI?.getColor(h[t]).hax??T:T,a.fill()}}}a.restore()}}function k(t){return new v(t)}let O=function(t){return k(t)},D=O;function F(t,e,a){return[t+(t-e)*Math.cos(a),t+(t-e)*Math.sin(a)]}function U(t){let e=0;for(let a of t.values)a>e&&(e=a);return e}class G extends n{getSpecialTickConfig(t){}getAngleOffset(){return 0}init(t){super.init(t),this.state.canvas.style.transform="scaleY(1)";let e=t.fillStyle??o.FILL_STYLE,a=t.fillStylePrimary??o.FILL_STYLE_PRIMARY,r=t.fillStyleTransparentBase??o.FILL_STYLE_TRANSPARENT_BASE;this.updateProps({fillStyle:e,fillStylePrimary:a,fillStyleTransparentBase:r,baseWidth:6,padding:60,ticksStep:6,ticksRenderLength:5,markedAngles:Array.from({length:12},(t,e)=>30*e),color2intensity:i(a),data:{series:[],range:[0,0],polygon:[],yawAngle:0,isNorthFacing:!0}})}clear(){this.clearRect()}resize(){super.resize(),this.state.markedAngles&&this.drawBackground(),this.state.data&&this.drawTicks()}render(t){let e=this.state.data,a={...e,...t},r=void 0!==t.yawAngle&&t.yawAngle!==e?.yawAngle||void 0!==t.isNorthFacing&&t.isNorthFacing!==e?.isNorthFacing;this.state.data=a,r&&this.drawTicks(),this.draw()}drawTicks(){let{canvas:t,baseWidth:e,fillStyle:a,fillStylePrimary:r,ticksStep:i,ticksRenderLength:l,markedAngles:s,data:n}=this.state,{yawAngle:o=0,isNorthFacing:h=!0}=n,u=document.createElement("canvas");u.width=t.width,u.height=t.height;let c=u.getContext("2d");if(!c)return;let d=t.width/2,f=h?0:-o;for(let t=0;t<360;t+=i){let r=(t+f)*Math.PI/180,i=s.includes(t),[n,o]=F(d,3.5*e+l*(i?1.2:1)+3*!!i,r),[h,u]=F(d,3.5*e,r);c.beginPath(),c.moveTo(n,o),c.lineTo(h,u),c.strokeStyle=a,c.lineWidth=i?2:.5,c.stroke()}c.font=`${2*e}px Arial`,c.textAlign="center",c.textBaseline="middle",s.forEach(t=>{let[r,i]=F(d,8*e,(t+f-90)*Math.PI/180),l=this.getSpecialTickConfig(t);l?(c.fillStyle=l.color,c.fillText(l.alias,r,i)):(c.fillStyle=a,c.fillText(t.toString(),r,i))});let g=t.width/18;c.translate(t.width/2,t.height/2),c.scale(1,-1),c.rotate((h?-o:0)*Math.PI/180),c.lineJoin="round",c.lineCap="round",c.strokeStyle=r,c.fillStyle=r,c.lineWidth=g/6,c.beginPath(),c.moveTo(0,g/2),c.lineTo(g/2,-g/2),c.lineTo(0,g/2-1/((1+Math.sqrt(5))/2)*g),c.lineTo(-g/2,-g/2),c.lineTo(0,g/2),c.fill(),c.stroke(),this.state.ticksCanvas=u}drawBackground(){let{canvas:t,baseWidth:e,padding:a,fillStyleTransparentBase:r}=this.state,i=document.createElement("canvas");i.width=t.width,i.height=t.height;let l=i.getContext("2d");if(!l)return;let s=t.width/2;l.beginPath(),l.arc(s,s,s-2*e,0,2*Math.PI),l.strokeStyle=r,l.lineWidth=e,l.stroke(),[,,,,,].fill(1).forEach((t,i)=>{l.beginPath(),l.arc(s,s,(s-a)*(i+1)/5,0,2*Math.PI),l.strokeStyle=r,l.lineWidth=e/4,l.stroke()}),this.state.BGCanvas=i}draw(){let{data:t,canvas:e,ctx:a,baseWidth:r,padding:i,BGCanvas:l,ticksCanvas:s,color2intensity:n,fillStylePrimary:o,fillStyleTransparentBase:h}=this.state,{series:u,range:c,polygon:d,isNorthFacing:f=!0,yawAngle:g=0}=t;if(!u&&!d)return;this.clearRect();let p=e.width/2,m=f?0:-g,T=this.getAngleOffset();if(l&&a.drawImage(l,0,0),c){let t=360/c.length;c.forEach((e,r)=>{let l=(r*t+m+T)*Math.PI/180,s=((r+1)*t+m+T)*Math.PI/180;a.beginPath(),a.moveTo(p,p),a.arc(p,p,p-i,l,s),a.closePath(),a.fillStyle=n[e],a.fill()})}if(d?.length)for(let t of[...d].sort((t,e)=>U(e)-U(t))){let{values:e,color:l,showDots:s}=t;if(!e||e.length<3)continue;let n=360/e.length,h=l??o;a.beginPath();let u=[];e.forEach((t,e)=>{let r=Math.max(0,Math.min(1,t))*(p-i),[l,s]=F(p,p-r,(e*n+m+T)*Math.PI/180);u.push([l,s]),0===e?a.moveTo(l,s):a.lineTo(l,s)}),a.closePath(),a.save();let c=a.createRadialGradient(p,p,0,p,p,p-i);if(c.addColorStop(0,"transparent"),c.addColorStop(1,h),a.fillStyle=c,a.fill(),a.strokeStyle=h,a.lineWidth=1,a.lineJoin="round",a.lineCap="round",a.stroke(),!1!==s){a.fillStyle=h;let t=r/2;for(let[e,r]of u)a.beginPath(),a.arc(e,r,t,0,2*Math.PI),a.fill()}a.restore()}u?.forEach(({value:t,color:e,lineWidth:l=r,radio:s=1})=>{if(null==t)return;let n=(t+m)*Math.PI/180-Math.PI/2,[o,u]=F(p,p,n),[c,d]=F(p,i+(1-s)*(p-i)+l/2,n);1!==s&&(a.beginPath(),a.moveTo(...F(p,i+l/2,n)),a.lineTo(c,d),a.strokeStyle=h,a.stroke()),a.beginPath(),a.moveTo(c,d),a.lineTo(o,u),a.strokeStyle=e,a.lineWidth=l,a.lineJoin="round",a.lineCap="round",a.stroke()}),s&&a.drawImage(s,0,0)}}let B={0:{color:o.DIAL_NORTH_COLOR,alias:"北"},180:{color:o.DIAL_SOUTH_COLOR,alias:"南"}};class X extends G{getSpecialTickConfig(t){return B[t]}getAngleOffset(){return -90}}class W extends G{getSpecialTickConfig(t){}getAngleOffset(){return 0}}class z extends n{init(t){super.init(t),this.updateProps({lineColor:t.lineColor??o.LINE_COLOR,pointColor:t.pointColor??o.POINT_COLOR})}clear(){this.clearRect(),this.updateProps({data:{IData:[],QData:[]}})}render(t){t.IData&&t.QData&&(this.state.data=t,this.draw())}draw(){let{data:t,canvas:e,pointColor:a,lineColor:r,ctx:i}=this.state;if(!t)return;let{IData:l,QData:s}=t;if(0===l.length||0===s.length)return;let{width:n,height:o}=i.canvas;i.clearRect(0,0,e.width,e.height);let[h,u]=T(l),[c,d]=T([h,u,...s]),f=n/(u-h),g=o/(d-c),p=l.map((t,e)=>({x:(t-h)*f,y:(s[e]-c)*g}));i.beginPath(),p.forEach((t,e)=>{i[0===e?"moveTo":"lineTo"](t.x,t.y)}),i.strokeStyle=r,i.lineWidth=1,i.stroke(),p.forEach(t=>{i.beginPath(),i.arc(t.x,t.y,2,0,2*Math.PI),i.fillStyle=a,i.fill()})}}let H=4,Y=256,V=t=>{let e=[];for(let a=0;a<256;a++){let i=a/255,l=t.length-1,s=Math.min(Math.floor(i*l),l-1),n=i*l-s,o=r(t[s]),h=r(t[s+1]);e.push({r:Math.round(o.r+(h.r-o.r)*n),g:Math.round(o.g+(h.g-o.g)*n),b:Math.round(o.b+(h.b-o.b)*n)})}return e},$=()=>{let t=V(u.CLASSIC_COLORS),e=new Uint8ClampedArray((u.MAX_ACCUMULATION+1)*4),a=t.length-1;for(let r=1;r<=u.MAX_ACCUMULATION;r++){let i=t[Math.min(Math.floor(Math.min(r*u.CLASSIC_ALPHA_GAIN/255,1)*a),a)],l=4*r;e[l]=i.r,e[l+1]=i.g,e[l+2]=i.b,e[l+3]=Math.min(r*u.CLASSIC_ALPHA_GAIN,255)}return e},Q=$();class q extends n{init(t){super.init(t),this.state.iqEyeMode=this.resolveMode(t.iqEyeMode),this.initColorLookup()}resize(){super.resize();let{width:t,height:e}=this.state.canvas;this.state.accumulator=new Uint16Array(t*e),this.state.imageData=this.state.ctx.createImageData(t,e)}clear(){this.clearRect(),this.state.accumulator?.fill(0),this.updateProps({data:{IData:[],QData:[]}})}render(t){t.IData?.length&&t.QData?.length&&(this.state.data=t,this.state.accumulator&&("classic"===this.state.iqEyeMode?this.state.accumulator.fill(0):this.applyDecay(),this.accumulateData(t),this.draw()))}draw(){if("classic"===this.state.iqEyeMode){this.drawClassic();return}this.drawModern()}drawClassic(){let{accumulator:t,imageData:e,ctx:a}=this.state;if(!t||!e)return;let r=e.data;for(let e=0;e<t.length;e++){let a=t[e],i=4*e;if(a>0){let t=4*a;r[i]=Q[t],r[i+1]=Q[t+1],r[i+2]=Q[t+2],r[i+3]=Q[t+3]}else r[i+3]=0}a.putImageData(e,0,0)}drawModern(){let{accumulator:t,imageData:e,ctx:a,colorLookup:r}=this.state;if(!t||!e||!r)return;let i=e.data,l=u.MAX_ACCUMULATION;for(let e=0;e<t.length;e++){let a=t[e],s=4*e;if(a>0){let t=Math.min(a/l,1),e=Math.min(Math.floor(t*(r.length-1)),r.length-1),n=r[e];i[s]=n.r,i[s+1]=n.g,i[s+2]=n.b,i[s+3]=Math.round(255*Math.min(2*t,1))}else i[s+3]=0}a.putImageData(e,0,0)}initColorLookup(){let t="classic"===this.state.iqEyeMode?u.CLASSIC_COLORS:u.MODERN_COLORS;this.state.colorLookup=V(t)}resolveMode(t){return"modern"===t?"modern":u.MODE}applyDecay(){let{accumulator:t}=this.state;if(!t)return;let e=u.DECAY_FACTOR;for(let a=0;a<t.length;a++)t[a]=Math.floor(t[a]*e)}accumulateData(t){let{accumulator:e,canvas:a}=this.state;if(!e)return;let{width:r,height:i}=a,[l,s]=u.Y_RANGE,n=s-l,o=u.SEGMENT_SIZE,h=r/(o-1);this.accumulateSegments(t.IData,l,n,r,i,o,h,e),this.accumulateSegments(t.QData,l,n,r,i,o,h,e)}accumulateSegments(t,e,a,r,i,l,s,n){let o=Math.ceil(t.length/l);for(let h=0;h<o;h++){let o=-1,u=-1;for(let c=0;c<l;c++){let d=h*l+c;if(d>=t.length)break;let f=t[d];if(void 0===f)continue;let g=Math.round(c*s),p=Math.round((f-e)/a*(i-1));o>=0&&this.drawLine(o,u,g,p,r,i,n),o=g,u=p}}}drawLine(t,e,a,r,i,l,s){let n=Math.abs(a-t),o=Math.abs(r-e),h=t<a?1:-1,c=e<r?1:-1,d=t,f=e,g=n-o,p=u.MAX_ACCUMULATION;for(;;){if(d>=0&&d<i&&f>=0&&f<l){let t=f*i+d;s[t]<p&&s[t]++}if(d===a&&f===r)break;let t=2*g;t>-o&&(g-=o,d+=h),t<n&&(g+=n,f+=c)}}}export{s as ColorInterpolator,X as Dial,p as Fluorescence,g as FluorescenceRenderMode,L as Gauge,d as GraphicType,y as Heatmap,x as HeatmapCanvas,P as HeatmapWebGL,z as IQ,q as IQEye,c as OrientationType,W as Radar,f as RendererType,D as Series,v as SeriesCanvas,i as color2intensity,b as createHeatmap,k as createSeries,e as hexToRGBA,a as rgbToHex};
324
+ `;class K extends I{disposed=!1;fallbackHandler;ringStaging=new Float32Array(0);programLocations=null;drawFrame=null;scheduleDraw(){if(null===this.drawFrame){if("function"!=typeof requestAnimationFrame){this.drawSafely();return}this.drawFrame=requestAnimationFrame(()=>{this.drawFrame=null,this.drawSafely()})}}drawSafely(){try{this.draw()}catch(e){this.requestFallback(e)}}cancelScheduledDraw(){null!==this.drawFrame&&("function"==typeof cancelAnimationFrame&&cancelAnimationFrame(this.drawFrame),this.drawFrame=null)}resolveProgramLocations(e,t){let a=e.getAttribLocation(t,"a_position"),r=e.getUniformLocation(t,"u_dataTexture"),i=e.getUniformLocation(t,"u_colorTexture"),n=e.getUniformLocation(t,"u_rangeMin"),l=e.getUniformLocation(t,"u_rangeMax"),o=e.getUniformLocation(t,"u_ringHead"),s=e.getUniformLocation(t,"u_ringValidCount"),h=e.getUniformLocation(t,"u_ringCapacity"),u=e.getUniformLocation(t,"u_dataHeight"),d=e.getUniformLocation(t,"u_tileWidth"),c=e.getUniformLocation(t,"u_ringMode");return!(a<0)&&r&&i&&n&&l&&o&&s&&h&&u&&d&&c?{position:a,dataTexture:r,colorTexture:i,rangeMin:n,rangeMax:l,ringHead:o,ringValidCount:s,ringCapacity:h,dataHeight:u,tileWidth:d,ringMode:c}:null}init(e){super.init(e);let{canvas:t,gl:a}=this.state;if(!a)throw Error("WebGL 2.0 is not available");let r=e.colors,i=r&&r.length>0?r:u.FLUORESCENCE_COLORS,n=this.createProgram({vertex:Q,fragment:J}),s=this.createQuadBuffer(),h=this.createTexture(),d=this.createTexture(),c=a.getParameter(a.MAX_TEXTURE_SIZE);if(!n||!s||!h||!d||!Number.isFinite(c)||c<=0)throw n&&a.deleteProgram(n),s&&a.deleteBuffer(s),h&&a.deleteTexture(h),d&&a.deleteTexture(d),t.remove(),Error("Failed to allocate heatmap WebGL resources");let f=this.resolveProgramLocations(a,n);if(!f)throw a.deleteProgram(n),a.deleteBuffer(s),a.deleteTexture(h),a.deleteTexture(d),t.remove(),Error("Failed to resolve heatmap WebGL program locations");if(this.programLocations=f,h){a.activeTexture(a.TEXTURE0),a.bindTexture(a.TEXTURE_2D,h);let e=new Float32Array([Z]);a.texImage2D(a.TEXTURE_2D,0,a.R32F,1,1,0,a.RED,a.FLOAT,e)}let[g,p]=e.range??u.RANGE,m=new l(i,g,p,.1);this.updateProps({data:[],colors:i,program:n,quadBuffer:s,dataTexture:h,dataTextures:h?[h]:[],dataTiles:[],colorTexture:d,dataWidth:0,dataHeight:0,CI:m,maxTextureSize:c,ringMode:!1,ringCapacity:0,ringHead:0,ringCount:0}),a.enable(a.BLEND),a.blendFunc(a.SRC_ALPHA,a.ONE_MINUS_SRC_ALPHA),this.updateColorTexture(),a.clearColor(0,0,0,0),a.clear(a.COLOR_BUFFER_BIT),this.resize(),t.__rfkitHeatmap=this,o(t,this)}setFallbackHandler(e){this.state.canvas.removeEventListener("webglcontextlost",this.handleContextLost),this.fallbackHandler=e,this.state.canvas.addEventListener("webglcontextlost",this.handleContextLost)}handleContextLost=e=>{e.preventDefault(),this.disposed||this.requestFallback(Error("Heatmap WebGL context lost"))};requestFallback(e){if(this.fallbackHandler){this.fallbackHandler(e);return}throw e instanceof Error?e:Error(String(e))}updateColorTexture(){let{gl:e,colorTexture:t,CI:a,range:r}=this.state;if(!e||!t||!a)return;let[i,n]=r,l=new Uint8Array(1024);for(let e=0;e<256;e++){let t=i+e/255*(n-i),r=a.getColor(t);l[4*e]=r.r,l[4*e+1]=r.g,l[4*e+2]=r.b,l[4*e+3]=r.a}e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,t),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,256,1,0,e.RGBA,e.UNSIGNED_BYTE,l)}updateDataTexture(){let{gl:e,data:t}=this.state;if(!e||0===t.length)return;let a=t.length,r=0;for(let e=0;e<a;e++){let a=t[e]?.length??0;a>r&&(r=a)}if(0===r)return;let i=this.state.maxTextureSize||e.getParameter(e.MAX_TEXTURE_SIZE);if(!i||i<=0)return;let n=Math.ceil(r/i),l=Math.ceil(a/i),o=n*l,s=this.state.dataTextures??[];if(s.length<o){let e=o-s.length;for(let t=0;t<e;t++){let e=this.createTexture();e&&s.push(e)}}else if(s.length>o){for(let t=o;t<s.length;t++)e.deleteTexture(s[t]);s=s.slice(0,o)}let h=[];e.activeTexture(e.TEXTURE0);for(let o=0;o<l;o++){let l=o*i,u=Math.min(i,a-l);for(let a=0;a<n;a++){let d=a*i,c=Math.min(i,r-d),f=o*n+a,g=s[f];if(!g)continue;let p=new Float32Array(c*u);for(let e=0;e<u;e++){let a=t[l+e],r=a?.length??0,i=e*c;for(let e=0;e<c;e++){let t=d+e;if(0===r||t>=r){p[i+e]=Z;continue}let n=a[t];void 0===n||Number.isNaN(n)?p[i+e]=Z:p[i+e]=n}}e.bindTexture(e.TEXTURE_2D,g),e.texImage2D(e.TEXTURE_2D,0,e.R32F,c,u,0,e.RED,e.FLOAT,p),h[f]={startCol:d,startRow:l,width:c,height:u}}}this.updateProps({dataTexture:s[0]??null,dataTextures:s,dataTiles:h,dataWidth:r,dataHeight:a})}ensureRingLayout(e,t){let{gl:a,maxTextureSize:r}=this.state;if(!a||e<=0||t<=0||t>r)return!1;let i=Math.ceil(e/r);if(this.state.ringMode&&this.state.ringCapacity===t&&this.state.dataWidth===e&&this.state.dataTextures.length===i)return!0;let n=[],l=[];a.activeTexture(a.TEXTURE0);for(let o=0;o<i;o++){let i=o*r,s=Math.min(r,e-i),h=this.createTexture();if(!h){for(let e of n)a.deleteTexture(e);return!1}let u=new Float32Array(s*t);u.fill(Z),a.bindTexture(a.TEXTURE_2D,h),a.texImage2D(a.TEXTURE_2D,0,a.R32F,s,t,0,a.RED,a.FLOAT,u),n.push(h),l.push({startCol:i,startRow:0,width:s,height:t})}for(let e of this.state.dataTextures)a.deleteTexture(e);return this.updateProps({data:[],dataTexture:n[0]??null,dataTextures:n,dataTiles:l,dataWidth:e,dataHeight:t,ringMode:!0,ringCapacity:t,ringHead:0,ringCount:0}),!0}uploadRingRow(e,t){let{gl:a,dataTextures:r,dataTiles:i,dataWidth:n}=this.state;if(a&&!(n<=0)){a.activeTexture(a.TEXTURE0);for(let l=0;l<i.length;l++){let o;let s=i[l],h=r[l];if(s&&h){if(e instanceof Float32Array&&e.length>=n)o=e.subarray(s.startCol,s.startCol+s.width);else{this.ringStaging.length<s.width&&(this.ringStaging=new Float32Array(s.width)),o=this.ringStaging;for(let t=0;t<s.width;t++){let a=e[s.startCol+t];o[t]=void 0===a||Number.isNaN(a)?Z:a}}a.bindTexture(a.TEXTURE_2D,h),a.texSubImage2D(a.TEXTURE_2D,0,0,t,s.width,1,a.RED,a.FLOAT,o)}}}}uploadRingReplace(e,t){let{gl:a,dataTextures:r,dataTiles:i}=this.state;if(!a)return 0;let n=0;for(;n<e.length&&0===e[n].length;)n+=1;let l=Math.min(t,e.length-n);a.activeTexture(a.TEXTURE0);for(let o=0;o<i.length;o++){let s=i[o],h=r[o];if(!s||!h)continue;let u=new Float32Array(s.width*t);u.fill(Z);for(let t=0;t<l;t++){let a=e[n+t],r=t*s.width;if(a instanceof Float32Array){let e=Math.min(a.length,s.startCol+s.width);e>s.startCol&&u.set(a.subarray(s.startCol,e),r)}else for(let e=0;e<s.width;e++){let t=a[s.startCol+e];u[r+e]=void 0===t||Number.isNaN(t)?Z:t}}a.bindTexture(a.TEXTURE_2D,h),a.texSubImage2D(a.TEXTURE_2D,0,0,0,s.width,t,a.RED,a.FLOAT,u)}return l}replace(e,t=e.length,a=e.reduce((e,t)=>Math.max(e,t.length),0)){if(t<=0||a<=0){this.clear();return}if(!this.ensureRingLayout(a,t)){this.render(e.map(e=>Array.from(e)));return}let r=this.uploadRingReplace(e,t);this.state.data=[],this.state.ringMode=!0,this.state.ringHead=0,this.state.ringCount=r,this.draw()}append(e,t=this.state.ringCapacity||1){let a=e.length;if(a<=0||t<=0)return;if(!this.ensureRingLayout(a,t)){let a=this.state.data.slice(-Math.max(0,t-1));a.push(Array.from(e)),this.render(a);return}let{ringCapacity:r,ringCount:i,ringHead:n}=this.state;this.uploadRingRow(e,i<r?(n+i)%r:n),i<r?this.state.ringCount=i+1:this.state.ringHead=(n+1)%r,this.state.data=[],this.state.ringMode=!0,this.scheduleDraw()}updateProps(e){super.updateProps(e);let t=e.colors??this.state.colors,a=e.range??this.state.range;if((e.colors||e.range)&&t.length>0){let[e,r]=a;this.state.CI?this.state.CI.setColors(t,e,r,.1):this.state.CI=new l(t,e,r,.1),this.updateColorTexture()}}setRange(e){e&&(this.updateProps({range:e}),this.draw())}clear(){this.cancelScheduledDraw(),this.clearRect(),this.updateProps({data:[],ringHead:0,ringCount:0})}render(e){e?.length>0&&(this.state.data=e,this.state.ringMode=!1,this.state.ringHead=0,this.state.ringCount=0,this.updateDataTexture(),this.draw())}draw(){this.cancelScheduledDraw();let{gl:e,program:t,quadBuffer:a,dataTextures:r,dataTiles:i,colorTexture:n,data:l,range:o,dataWidth:s,dataHeight:h,canvas:u,ringMode:d,ringHead:c,ringCount:f,ringCapacity:g}=this.state;if(!e||!t||!a||!n)return;let p=this.programLocations??this.resolveProgramLocations(e,t);if(!p||(this.programLocations=p,e.clearColor(0,0,0,0),e.clear(e.COLOR_BUFFER_BIT),!d&&0===l.length||d&&0===f||0===s||0===h||0===i.length||0===r.length))return;let[m,E]=o;e.useProgram(t),e.bindBuffer(e.ARRAY_BUFFER,a),e.enableVertexAttribArray(p.position),e.vertexAttribPointer(p.position,2,e.FLOAT,!1,0,0),e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,n),e.uniform1i(p.colorTexture,1),e.uniform1f(p.rangeMin,m),e.uniform1f(p.rangeMax,E),e.uniform1i(p.ringHead,c),e.uniform1i(p.ringValidCount,f),e.uniform1i(p.ringCapacity,g),e.uniform1i(p.dataHeight,h),e.uniform1i(p.ringMode,+!!d);let T=u.width,_=u.height,x=e=>Math.round(e/s*T),M=e=>Math.round(e/h*_);for(let t=0;t<i.length;t++){let a=i[t],n=r[t];if(!a||!n)continue;let l=x(a.startCol),o=x(a.startCol+a.width),s=M(a.startRow),h=_-M(a.startRow+a.height),u=o-l,c=_-s-h;u<=0||c<=0||(d?e.viewport(l,0,u,_):e.viewport(l,h,u,c),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,n),e.uniform1i(p.dataTexture,0),e.uniform1i(p.tileWidth,a.width),e.drawArrays(e.TRIANGLES,0,6))}e.viewport(0,0,T,_)}resize(){super.resize(!1),this.drawSafely()}toDataURL(e="image/png",t){try{this.draw();let{canvas:a}=this.state,r=document.createElement("canvas");r.width=a.width,r.height=a.height;let i=r.getContext("2d");if(!i)return null;return i.translate(0,a.height),i.scale(1,-1),i.drawImage(a,0,0),r.toDataURL(e,t)}catch(e){return this.requestFallback(e),null}}dispose(){if(this.disposed)return;this.disposed=!0,this.cancelScheduledDraw();let{canvas:e,gl:t,program:a,quadBuffer:r,dataTextures:i,colorTexture:n}=this.state;if(s(e,this),e?.removeEventListener("webglcontextlost",this.handleContextLost),e.__rfkitHeatmap===this&&delete e.__rfkitHeatmap,t){for(let e of(a&&t.deleteProgram(a),r&&t.deleteBuffer(r),i))t.deleteTexture(e);n&&t.deleteTexture(n)}this.programLocations=null,this.fallbackHandler=void 0,super.dispose()}}class j extends H{init(e){super.init(e);let{colors:t}=e;this.updateProps({colors:t,data:[]}),this.resize()}updateProps(e){super.updateProps(e);let{range:t=this.state.range,colors:a=this.state.colors}=e;if(t&&a){let[e,r]=t;_(e)&&_(r)&&e<r&&(this.state.CI?this.state.CI.setColors(a,e,r,.1):this.updateProps({CI:new l(a,e,r,.1)}))}}clearImageData(){let{ctx:e,canvas:{height:t,width:a}}=this.state;t&&a&&this.updateProps({imageData:e.createImageData(a,t)})}clearRect(){super.clearRect(),this.clearImageData()}clear(){this.clearRect(),this.updateProps({data:[]})}resize(){super.resize(),this.clearImageData()}setRange(e){e&&(this.updateProps({range:e}),this.draw())}render(e){e?.length>=0&&(this.state.data=e,this.draw())}draw(){let{imageData:e,data:t,canvas:a,ctx:r,CI:i}=this.state;if(!e||0===t.length){r.clearRect(0,0,a.width,a.height);return}E(a.width,a.height,t,e.data,e=>i.getColor(e)),r.putImageData(e,0,0)}}class ee{append;replace;backend;disposed=!1;fallingBack=!1;latestData=null;props;ringCapacity=0;ringCount=0;ringFrames=[];ringHead=0;ringWidth=0;constructor(e){this.props={...e},this.backend=this.createBackend(e),this.backend instanceof K&&(this.append=(e,t)=>this.appendFrame(e,t),this.replace=(e,t)=>this.replaceFrames(e,t))}get state(){return this.backend.state}createBackend(e){if((e.renderer??p.WebGL)===p.WebGL){if(C())try{let t=new K(e);return t.setFallbackHandler(e=>this.fallbackToCanvas(e)),t}catch(e){console.warn("[Heatmap] WebGL initialization failed, falling back to Canvas",e)}else console.warn("[Heatmap] WebGL 2.0 not supported, falling back to Canvas")}return new j(e)}fallbackToCanvas(e){if(!this.disposed&&!this.fallingBack&&!(this.backend instanceof j)){this.fallingBack=!0;try{let{colors:t,range:a}=this.backend.state;console.warn("[Heatmap] WebGL rendering failed, falling back to Canvas",e),this.backend.dispose(),this.props={...this.props,colors:t,range:a,renderer:p.Canvas},this.backend=new j(this.props);let r=this.ringCapacity>0?this.materializeRingFrames():this.latestData;r&&this.backend.render(r)}finally{this.fallingBack=!1}}}resetRingFrames(){this.ringCapacity=0,this.ringCount=0,this.ringFrames=[],this.ringHead=0,this.ringWidth=0}initializeRingFrames(e,t){this.ringCapacity=e,this.ringCount=0,this.ringFrames=Array(e),this.ringHead=0,this.ringWidth=t}trackReplacement(e,t){let a=0;for(;a<e.length&&0===e[a].length;)a+=1;let r=Math.min(t,e.length-a),i=0;for(let t=0;t<r;t+=1)i=Math.max(i,e[a+t].length);if(t<=0||i<=0){this.resetRingFrames();return}this.initializeRingFrames(t,i);for(let t=0;t<r;t+=1)this.ringFrames[t]=e[a+t];this.ringCount=r}trackAppend(e,t){let a=e.length;if(t<=0||a<=0)return;(this.ringCapacity!==t||this.ringWidth!==a)&&this.initializeRingFrames(t,a);let r=this.ringCount<this.ringCapacity?(this.ringHead+this.ringCount)%this.ringCapacity:this.ringHead;this.ringFrames[r]=e,this.ringCount<this.ringCapacity?this.ringCount+=1:this.ringHead=(this.ringHead+1)%this.ringCapacity}materializeRingFrames(){let e=Array.from({length:this.ringCapacity},()=>[]),t=this.ringCapacity-this.ringCount;for(let a=0;a<this.ringCount;a+=1){let r=(this.ringHead+a)%this.ringCapacity,i=this.ringFrames[r];i&&(e[t+a]=Array.from(i))}return e}handleBackendError(e){if(this.backend instanceof K){this.fallbackToCanvas(e);return}throw e}replaceFrames(e,t=e.length){this.latestData=null,this.trackReplacement(e,t);try{if(this.backend instanceof K){this.backend.replace(e,t);return}this.backend.render(this.materializeRingFrames())}catch(e){this.handleBackendError(e)}}appendFrame(e,t=this.ringCapacity||1){this.latestData=null,this.trackAppend(e,t);try{if(this.backend instanceof K){this.backend.append(e,t);return}this.backend.render(this.materializeRingFrames())}catch(e){this.handleBackendError(e)}}updateProps(e){void 0!==e.colors&&(this.props.colors=e.colors),void 0!==e.range&&(this.props.range=e.range);try{this.backend.updateProps(e)}catch(e){this.handleBackendError(e)}}render(e){this.latestData=e,this.resetRingFrames();try{this.backend.render(e)}catch(e){this.handleBackendError(e)}}setRange(e){this.props.range=e;try{this.backend.setRange(e)}catch(e){this.handleBackendError(e)}}clear(){this.latestData=null,this.resetRingFrames();try{this.backend.clear()}catch(e){this.handleBackendError(e)}}resize(){try{this.backend.resize()}catch(e){this.handleBackendError(e)}}dispose(){this.disposed||(this.disposed=!0,this.latestData=null,this.resetRingFrames(),this.backend.dispose())}}function et(e){return new ee(e)}let ea=function(e){return et(e)},er=ea,ei=!1;class en extends H{init(e){super.init(e);let{series:t,barValue2Color:a,disabledClearRect:r,colors:i}=e;this.updateProps({interval:0,series:{},barValue2Color:a,disabledClearRect:r,colors:i}),this.setRange(this.state.range),Array.isArray(t)&&t.forEach(e=>{this.setSeries(e)})}updateProps(e){super.updateProps(e);let{colors:t=this.state.colors}=e;this.state.barValue2Color&&!this.state.CI&&t&&this.updateProps({CI:new l(t,0,100,.1)})}clear(){super.clearRect();let{series:e}=this.state;Object.entries(e).forEach(([e,t])=>{this.setSeries({...t,name:e,data:void 0})})}setRange(e){this.updateProps({range:[e[0],e[1],e[1]-e[0]]}),this.draw()}setIntervel(e){e&&this.updateProps({interval:e})}setSeries(e){if(e?.name){let{name:t}=e,{series:a}=this.state;a[t]={thickness:u.THICKNESS,display:!0,color:u.COLOR,type:g.Line,data:void 0,path:void 0,orientation:f.Vertical,...a[t],...e},this.draw()}}render(e){e&&this.setSeries(e),this.draw()}draw(){let{series:e,ctx:a,disabledClearRect:r}=this.state;r||this.clearRect();let i=Object.values(e),{range:n,canvas:l}=this.state,o=n[0],s=n[1],{width:h,height:d}=l,c=s-o;a.save(),a.translate(.5,.5);for(let e=0;e<i.length;e+=1){let r=i[e],{display:n,type:l,data:s,orientation:p}=r,m=r.thickness??u.THICKNESS,E=r.color??u.COLOR;if(!s||!n)continue;let T=s.length,_=h/T;switch(l){case g.Line:case g.Stepline:a.lineWidth=m,a.strokeStyle=E;break;case g.Circle:case g.Rect:case g.Bar:case g.Area:a.fillStyle=E}if(l===g.Line){let e=[];for(let t=0;t<T;t+=1){let r=s[t];if(void 0===r||Number.isNaN(r)){if(e.length>0){a.beginPath(),a.moveTo(e[0][0],e[0][1]);for(let t=1;t<e.length;t++)a.lineTo(e[t][0],e[t][1]);a.lineWidth=m,a.strokeStyle=E,a.stroke(),e=[]}}else{let a=Math.round((t+.5)*_),i=Math.round((r-o)/c*d);e.push([a,i])}}if(e.length>0){a.beginPath(),a.moveTo(e[0][0],e[0][1]);for(let t=1;t<e.length;t++)a.lineTo(e[t][0],e[t][1]);a.lineWidth=m,a.strokeStyle=E,a.stroke()}}if(l===g.Stepline){let e=[];for(let t=0;t<T;t+=1){let r=s[t];if(void 0===r||Number.isNaN(r)){if(e.length>0){a.beginPath(),a.moveTo(e[0][0],e[0][1]);for(let t=1;t<e.length;t++){let[r,i]=e[t-1],[n,l]=e[t];a.lineTo(n,i),a.lineTo(n,l)}let[t,r]=e[e.length-1],i=Math.round(t+_);a.lineTo(i,r),a.lineWidth=m,a.strokeStyle=E,a.stroke(),e=[]}}else{let a=Math.round(t*_),i=Math.round((r-o)/c*d);e.push([a,i])}}if(e.length>0){a.beginPath(),a.moveTo(e[0][0],e[0][1]);for(let t=1;t<e.length;t++){let[r,i]=e[t-1],[n,l]=e[t];a.lineTo(n,i),a.lineTo(n,l)}let[t,r]=e[e.length-1],i=Math.round(t+_);a.lineTo(i,r),a.lineWidth=m,a.strokeStyle=E,a.stroke()}}if(l===g.Circle){let e=+m/2,t=2*Math.PI;for(let r=0;r<T;r+=1){let i=Math.round((r+.5)*_),n=Math.round((s[r]-o)/c*d);a.beginPath(),a.arc(i,n,e,0,t),a.fillStyle=E,a.fill()}}if(l===g.Rect){let e=+m;for(let t=0;t<T;t+=1){let r=Math.round((t+.5)*_),i=Math.round((s[t]-o)/c*d);a.beginPath(),a.rect(r,i,e,e),a.fillStyle=E,a.fill()}}if(l===g.Area){let e=[],{r,g:i,b:n}=t(E??u.COLOR),l=e=>{if(0===e.length)return;let[t,l]=e[0],[o,s]=e[e.length-1],h=Math.round(o+_);a.beginPath(),a.moveTo(t,0),a.lineTo(t,l);for(let t=1;t<e.length;t++){let[r,i]=e[t-1],[n,l]=e[t];a.lineTo(n,i),a.lineTo(n,l)}a.lineTo(h,s),a.lineTo(h,0),a.closePath(),a.fillStyle=`rgba(${r}, ${i}, ${n}, 1)`,a.fill()};for(let t=0;t<T;t+=1){let a=s[t];void 0===a||Number.isNaN(a)?(l(e),e=[]):e.push([Math.round(t*_),Math.round((a-o)/c*d)])}l(e)}if(l===g.Bar){if(p===f.Horizontal){let e=d/T,t=Math.round(e);for(let r=0;r<T;r+=1){let i=Math.round(d-(r+1)*e),n=Math.round((s[r]-o)/c*h),l=h-n;a.beginPath(),a.rect(l,i,n,t),a.fillStyle=E,a.fill()}}else for(let e=0;e<T;e+=1){let t=Math.floor(e*h/T),r=Math.floor((e+1)*h/T)-t,i=Math.round((s[e]-o)/c*d);a.beginPath(),a.rect(t,0,r,i),a.fillStyle=this.state.barValue2Color?this.state.CI?.getColor(s[e]).hax??E:E,a.fill()}}}a.restore()}}function el(e){return new en(e)}let eo=function(e){return el(e)},es=eo;function eh(e,t,a){return[e+(e-t)*Math.cos(a),e+(e-t)*Math.sin(a)]}function eu(e){let t=0;for(let a of e.values)a>t&&(t=a);return t}class ed extends H{getSpecialTickConfig(e){}getAngleOffset(){return 0}init(e){super.init(e),this.state.canvas.style.transform="scaleY(1)";let t=e.fillStyle??u.FILL_STYLE,a=e.fillStylePrimary??u.FILL_STYLE_PRIMARY,r=e.fillStyleTransparentBase??u.FILL_STYLE_TRANSPARENT_BASE;this.updateProps({fillStyle:t,fillStylePrimary:a,fillStyleTransparentBase:r,baseWidth:6,padding:60,ticksStep:6,ticksRenderLength:5,markedAngles:Array.from({length:12},(e,t)=>30*t),color2intensity:i(a),data:{series:[],range:[0,0],polygon:[],yawAngle:0,isNorthFacing:!0}})}clear(){this.clearRect()}resize(){super.resize(),this.state.markedAngles&&this.drawBackground(),this.state.data&&this.drawTicks()}render(e){let t=this.state.data,a={...t,...e},r=void 0!==e.yawAngle&&e.yawAngle!==t?.yawAngle||void 0!==e.isNorthFacing&&e.isNorthFacing!==t?.isNorthFacing;this.state.data=a,r&&this.drawTicks(),this.draw()}drawTicks(){let{canvas:e,baseWidth:t,fillStyle:a,fillStylePrimary:r,ticksStep:i,ticksRenderLength:n,markedAngles:l,data:o}=this.state,{yawAngle:s=0,isNorthFacing:h=!0}=o,u=document.createElement("canvas");u.width=e.width,u.height=e.height;let d=u.getContext("2d");if(!d)return;let c=e.width/2,f=h?0:-s;for(let e=0;e<360;e+=i){let r=(e+f)*Math.PI/180,i=l.includes(e),[o,s]=eh(c,3.5*t+n*(i?1.2:1)+3*!!i,r),[h,u]=eh(c,3.5*t,r);d.beginPath(),d.moveTo(o,s),d.lineTo(h,u),d.strokeStyle=a,d.lineWidth=i?2:.5,d.stroke()}d.font=`${2*t}px Arial`,d.textAlign="center",d.textBaseline="middle",l.forEach(e=>{let[r,i]=eh(c,8*t,(e+f-90)*Math.PI/180),n=this.getSpecialTickConfig(e);n?(d.fillStyle=n.color,d.fillText(n.alias,r,i)):(d.fillStyle=a,d.fillText(e.toString(),r,i))});let g=e.width/18;d.translate(e.width/2,e.height/2),d.scale(1,-1),d.rotate((h?-s:0)*Math.PI/180),d.lineJoin="round",d.lineCap="round",d.strokeStyle=r,d.fillStyle=r,d.lineWidth=g/6,d.beginPath(),d.moveTo(0,g/2),d.lineTo(g/2,-g/2),d.lineTo(0,g/2-1/((1+Math.sqrt(5))/2)*g),d.lineTo(-g/2,-g/2),d.lineTo(0,g/2),d.fill(),d.stroke(),this.state.ticksCanvas=u}drawBackground(){let{canvas:e,baseWidth:t,padding:a,fillStyleTransparentBase:r}=this.state,i=document.createElement("canvas");i.width=e.width,i.height=e.height;let n=i.getContext("2d");if(!n)return;let l=e.width/2;n.beginPath(),n.arc(l,l,l-2*t,0,2*Math.PI),n.strokeStyle=r,n.lineWidth=t,n.stroke(),[,,,,,].fill(1).forEach((e,i)=>{n.beginPath(),n.arc(l,l,(l-a)*(i+1)/5,0,2*Math.PI),n.strokeStyle=r,n.lineWidth=t/4,n.stroke()}),this.state.BGCanvas=i}draw(){let{data:e,canvas:t,ctx:a,baseWidth:r,padding:i,BGCanvas:n,ticksCanvas:l,color2intensity:o,fillStylePrimary:s,fillStyleTransparentBase:h}=this.state,{series:u,range:d,polygon:c,isNorthFacing:f=!0,yawAngle:g=0}=e;if(!u&&!c)return;this.clearRect();let p=t.width/2,m=f?0:-g,E=this.getAngleOffset();if(n&&a.drawImage(n,0,0),d){let e=360/d.length;d.forEach((t,r)=>{let n=(r*e+m+E)*Math.PI/180,l=((r+1)*e+m+E)*Math.PI/180;a.beginPath(),a.moveTo(p,p),a.arc(p,p,p-i,n,l),a.closePath(),a.fillStyle=o[t],a.fill()})}if(c?.length)for(let e of[...c].sort((e,t)=>eu(t)-eu(e))){let{values:t,color:n,showDots:l}=e;if(!t||t.length<3)continue;let o=360/t.length,h=n??s;a.beginPath();let u=[];t.forEach((e,t)=>{let r=Math.max(0,Math.min(1,e))*(p-i),[n,l]=eh(p,p-r,(t*o+m+E)*Math.PI/180);u.push([n,l]),0===t?a.moveTo(n,l):a.lineTo(n,l)}),a.closePath(),a.save();let d=a.createRadialGradient(p,p,0,p,p,p-i);if(d.addColorStop(0,"transparent"),d.addColorStop(1,h),a.fillStyle=d,a.fill(),a.strokeStyle=h,a.lineWidth=1,a.lineJoin="round",a.lineCap="round",a.stroke(),!1!==l){a.fillStyle=h;let e=r/2;for(let[t,r]of u)a.beginPath(),a.arc(t,r,e,0,2*Math.PI),a.fill()}a.restore()}u?.forEach(({value:e,color:t,lineWidth:n=r,radio:l=1})=>{if(null==e)return;let o=(e+m)*Math.PI/180-Math.PI/2,[s,u]=eh(p,p,o),[d,c]=eh(p,i+(1-l)*(p-i)+n/2,o);1!==l&&(a.beginPath(),a.moveTo(...eh(p,i+n/2,o)),a.lineTo(d,c),a.strokeStyle=h,a.stroke()),a.beginPath(),a.moveTo(d,c),a.lineTo(s,u),a.strokeStyle=t,a.lineWidth=n,a.lineJoin="round",a.lineCap="round",a.stroke()}),l&&a.drawImage(l,0,0)}}let ec={0:{color:u.DIAL_NORTH_COLOR,alias:"北"},180:{color:u.DIAL_SOUTH_COLOR,alias:"南"}};class ef extends ed{getSpecialTickConfig(e){return ec[e]}getAngleOffset(){return -90}}class eg extends ed{getSpecialTickConfig(e){}getAngleOffset(){return 0}}class ep extends H{init(e){super.init(e),this.updateProps({lineColor:e.lineColor??u.LINE_COLOR,pointColor:e.pointColor??u.POINT_COLOR})}clear(){this.clearRect(),this.updateProps({data:{IData:[],QData:[]}})}render(e){e.IData&&e.QData&&(this.state.data=e,this.draw())}draw(){let{data:e,canvas:t,pointColor:a,lineColor:r,ctx:i}=this.state;if(!e)return;let{IData:n,QData:l}=e;if(0===n.length||0===l.length)return;let{width:o,height:s}=i.canvas;i.clearRect(0,0,t.width,t.height);let[h,u]=T(n),[d,c]=T([h,u,...l]),f=o/(u-h),g=s/(c-d),p=n.map((e,t)=>({x:(e-h)*f,y:(l[t]-d)*g}));i.beginPath(),p.forEach((e,t)=>{i[0===t?"moveTo":"lineTo"](e.x,e.y)}),i.strokeStyle=r,i.lineWidth=1,i.stroke(),p.forEach(e=>{i.beginPath(),i.arc(e.x,e.y,2,0,2*Math.PI),i.fillStyle=a,i.fill()})}}let em=4,eE=256,eT=e=>{let t=[];for(let a=0;a<256;a++){let i=a/255,n=e.length-1,l=Math.min(Math.floor(i*n),n-1),o=i*n-l,s=r(e[l]),h=r(e[l+1]);t.push({r:Math.round(s.r+(h.r-s.r)*o),g:Math.round(s.g+(h.g-s.g)*o),b:Math.round(s.b+(h.b-s.b)*o)})}return t},e_=()=>{let e=eT(c.CLASSIC_COLORS),t=new Uint8ClampedArray((c.MAX_ACCUMULATION+1)*4),a=e.length-1;for(let r=1;r<=c.MAX_ACCUMULATION;r++){let i=e[Math.min(Math.floor(Math.min(r*c.CLASSIC_ALPHA_GAIN/255,1)*a),a)],n=4*r;t[n]=i.r,t[n+1]=i.g,t[n+2]=i.b,t[n+3]=Math.min(r*c.CLASSIC_ALPHA_GAIN,255)}return t},ex=e_();class eM extends H{init(e){super.init(e),this.state.iqEyeMode=this.resolveMode(e.iqEyeMode),this.initColorLookup()}resize(){super.resize();let{width:e,height:t}=this.state.canvas;this.state.accumulator=new Uint16Array(e*t),this.state.imageData=this.state.ctx.createImageData(e,t)}clear(){this.clearRect(),this.state.accumulator?.fill(0),this.updateProps({data:{IData:[],QData:[]}})}render(e){e.IData?.length&&e.QData?.length&&(this.state.data=e,this.state.accumulator&&("classic"===this.state.iqEyeMode?this.state.accumulator.fill(0):this.applyDecay(),this.accumulateData(e),this.draw()))}draw(){if("classic"===this.state.iqEyeMode){this.drawClassic();return}this.drawModern()}drawClassic(){let{accumulator:e,imageData:t,ctx:a}=this.state;if(!e||!t)return;let r=t.data;for(let t=0;t<e.length;t++){let a=e[t],i=4*t;if(a>0){let e=4*a;r[i]=ex[e],r[i+1]=ex[e+1],r[i+2]=ex[e+2],r[i+3]=ex[e+3]}else r[i+3]=0}a.putImageData(t,0,0)}drawModern(){let{accumulator:e,imageData:t,ctx:a,colorLookup:r}=this.state;if(!e||!t||!r)return;let i=t.data,n=c.MAX_ACCUMULATION;for(let t=0;t<e.length;t++){let a=e[t],l=4*t;if(a>0){let e=Math.min(a/n,1),t=Math.min(Math.floor(e*(r.length-1)),r.length-1),o=r[t];i[l]=o.r,i[l+1]=o.g,i[l+2]=o.b,i[l+3]=Math.round(255*Math.min(2*e,1))}else i[l+3]=0}a.putImageData(t,0,0)}initColorLookup(){let e="classic"===this.state.iqEyeMode?c.CLASSIC_COLORS:c.MODERN_COLORS;this.state.colorLookup=eT(e)}resolveMode(e){return"modern"===e?"modern":c.MODE}applyDecay(){let{accumulator:e}=this.state;if(!e)return;let t=c.DECAY_FACTOR;for(let a=0;a<e.length;a++)e[a]=Math.floor(e[a]*t)}accumulateData(e){let{accumulator:t,canvas:a}=this.state;if(!t)return;let{width:r,height:i}=a,[n,l]=c.Y_RANGE,o=l-n,s=c.SEGMENT_SIZE,h=r/(s-1);this.accumulateSegments(e.IData,n,o,r,i,s,h,t),this.accumulateSegments(e.QData,n,o,r,i,s,h,t)}accumulateSegments(e,t,a,r,i,n,l,o){let s=Math.ceil(e.length/n);for(let h=0;h<s;h++){let s=-1,u=-1;for(let d=0;d<n;d++){let c=h*n+d;if(c>=e.length)break;let f=e[c];if(void 0===f)continue;let g=Math.round(d*l),p=Math.round((f-t)/a*(i-1));s>=0&&this.drawLine(s,u,g,p,r,i,o),s=g,u=p}}}drawLine(e,t,a,r,i,n,l){let o=Math.abs(a-e),s=Math.abs(r-t),h=e<a?1:-1,u=t<r?1:-1,d=e,f=t,g=o-s,p=c.MAX_ACCUMULATION;for(;;){if(d>=0&&d<i&&f>=0&&f<n){let e=f*i+d;l[e]<p&&l[e]++}if(d===a&&f===r)break;let e=2*g;e>-s&&(g-=s,d+=h),e<o&&(g+=o,f+=u)}}}export{l as ColorInterpolator,ef as Dial,Y as Fluorescence,V as FluorescenceCanvas,m as FluorescenceRenderMode,W as FluorescenceWebGL,q as Gauge,g as GraphicType,er as Heatmap,j as HeatmapCanvas,K as HeatmapWebGL,ep as IQ,eM as IQEye,f as OrientationType,eg as Radar,p as RendererType,es as Series,en as SeriesCanvas,h as captureCanvasFrame,i as color2intensity,$ as createFluorescence,et as createHeatmap,el as createSeries,t as hexToRGBA,a as rgbToHex};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rfkit/renderer",
3
- "version": "0.2.3",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "A high-performance canvas rendering engine for charts and visualizations, supporting Canvas 2D and WebGL rendering",
6
6
  "exports": {
@@ -1,41 +1,63 @@
1
- import Engine from '../../core/Engine';
2
- import type { FluorescenceRenderData, FluorescenceState, InitProps, Range } from '../../types';
3
- import { FluorescenceRenderMode } from '../../types';
4
- export default class Fluorescence extends Engine<FluorescenceState> {
1
+ import type { ColorValue, FluorescenceRenderData, FluorescenceRendererState, InitProps, Range, RGBA } from '../../types';
2
+ import { type FluorescenceRenderMode } from '../../types';
3
+ import FluorescenceCanvas from './FluorescenceCanvas';
4
+ export type { FluorescenceRendererState } from '../../types';
5
+ /** 荧光谱 Canvas/WebGL 后端共享的公开接口。 */
6
+ export interface IFluorescence {
7
+ readonly state: FluorescenceRendererState;
5
8
  init(props: InitProps): void;
6
- updateProps(e: Partial<FluorescenceState>): void;
7
- /** 设置渲染模式 */
9
+ render(data: FluorescenceRenderData): void;
10
+ setRange(range: Range): void;
8
11
  setRenderMode(mode: FluorescenceRenderMode): void;
12
+ updateProps(props: Partial<FluorescenceRendererState>): void;
13
+ draw(): void;
9
14
  clearImageData(): void;
10
15
  clearRect(): void;
11
16
  clear(): void;
12
- dispose(): void;
13
- private drawBlocks;
14
17
  resize(): void;
15
- setRange(range: Range): void;
16
- render(data: FluorescenceRenderData): void;
17
- interpolateColor(colors: Array<string | {
18
- r: number;
19
- g: number;
20
- b: number;
21
- }>, ratio: number): {
18
+ dispose(): void;
19
+ interpolateColor(colors: ColorValue[], ratio: number): RGBA;
20
+ hexToRgb(hex: string): {
22
21
  r: number;
23
22
  g: number;
24
23
  b: number;
25
- a: number;
26
24
  };
27
- private colorCache;
25
+ }
26
+ /**
27
+ * 荧光谱统一入口。
28
+ *
29
+ * 默认优先使用 WebGL;能力不足、初始化失败或运行中上下文丢失时,
30
+ * 使用最后的数据和显示状态自动切换到二维画布。
31
+ */
32
+ export default class Fluorescence implements IFluorescence {
33
+ private backend;
34
+ private latestData;
35
+ private props;
36
+ private fallingBack;
37
+ private disposed;
38
+ constructor(props: InitProps);
39
+ get state(): FluorescenceRendererState;
40
+ /** 兼容旧类的显式初始化入口。 */
41
+ init(props: InitProps): void;
42
+ private createBackend;
43
+ private fallbackToCanvas;
44
+ updateProps(props: Partial<FluorescenceRendererState>): void;
45
+ render(data: FluorescenceRenderData): void;
46
+ setRange(range: Range): void;
47
+ setRenderMode(mode: FluorescenceRenderMode): void;
48
+ draw(): void;
49
+ /** Canvas 后端重建像素缓存;WebGL 后端无需对应缓存。 */
50
+ clearImageData(): void;
51
+ clearRect(): void;
52
+ clear(): void;
53
+ resize(): void;
54
+ interpolateColor(colors: ColorValue[], ratio: number): RGBA;
28
55
  hexToRgb(hex: string): {
29
56
  r: number;
30
57
  g: number;
31
58
  b: number;
32
59
  };
33
- private lastRenderParams;
34
- private intensityMatrixCache;
35
- private gridCentersCache;
36
- private weightLookupCache;
37
- private colorLookupCache;
38
- private blockPositionsCache;
39
- private lastBlockRenderParams;
40
- draw(): void;
60
+ dispose(): void;
41
61
  }
62
+ export declare const createFluorescence: (props: InitProps) => IFluorescence;
63
+ export { FluorescenceCanvas };
@@ -0,0 +1,40 @@
1
+ import Engine from '../../core/Engine';
2
+ import type { FluorescenceRenderData, FluorescenceState, InitProps, Range } from '../../types';
3
+ import { FluorescenceRenderMode } from '../../types';
4
+ export default class FluorescenceCanvas extends Engine<FluorescenceState> {
5
+ init(props: InitProps): void;
6
+ updateProps(e: Partial<FluorescenceState>): void;
7
+ /** 设置渲染模式 */
8
+ setRenderMode(mode: FluorescenceRenderMode): void;
9
+ clearImageData(): void;
10
+ clearRect(): void;
11
+ clear(): void;
12
+ dispose(): void;
13
+ private drawBlocks;
14
+ resize(): void;
15
+ setRange(range: Range): void;
16
+ render(data: FluorescenceRenderData): void;
17
+ interpolateColor(colors: Array<string | {
18
+ r: number;
19
+ g: number;
20
+ b: number;
21
+ }>, ratio: number): {
22
+ r: number;
23
+ g: number;
24
+ b: number;
25
+ a: number;
26
+ };
27
+ private colorCache;
28
+ hexToRgb(hex: string): {
29
+ r: number;
30
+ g: number;
31
+ b: number;
32
+ };
33
+ private intensityMatrixCache;
34
+ private gaussianGeometryCache;
35
+ private colorLookupCache;
36
+ private blockPositionsCache;
37
+ private lastBlockRenderParams;
38
+ private prepareGaussianGeometry;
39
+ draw(): void;
40
+ }
@@ -1,35 +1,24 @@
1
1
  import type { InitProps, Range } from '../../types';
2
2
  import HeatmapCanvas from './HeatmapCanvas';
3
- /** Heatmap 统一接口 */
3
+ /** 热力图 Canvas/WebGL 后端共享的公开接口。 */
4
4
  export interface IHeatmap {
5
5
  render(data: number[][]): void;
6
+ /** 可选能力:用完整帧集合替换增量纹理。 */
7
+ replace?(frames: readonly ArrayLike<number>[], capacity?: number): void;
8
+ /** 可选能力:向增量纹理追加一帧。 */
9
+ append?(frame: ArrayLike<number>, capacity?: number): void;
6
10
  setRange(range: Range): void;
7
11
  clear(): void;
8
12
  resize(): void;
9
13
  dispose(): void;
10
14
  }
11
- /** Heatmap 构造函数类型 */
15
+ /** 兼容函数调用和构造调用的热力图入口类型。 */
12
16
  interface HeatmapConstructor {
13
17
  new (props: InitProps): IHeatmap;
14
18
  (props: InitProps): IHeatmap;
15
19
  }
16
- /**
17
- * 创建 Heatmap 实例
18
- * 根据 renderer 参数自动选择 Canvas 2D 或 WebGL 实现
19
- * 如果请求 WebGL 但浏览器不支持,自动降级到 Canvas
20
- */
21
20
  export declare function createHeatmap(props: InitProps): IHeatmap;
22
- /**
23
- * Heatmap - 通过 renderer 参数选择实现
24
- * 支持 new Heatmap() 或 Heatmap() 两种调用方式
25
- *
26
- * @example
27
- * // WebGL 版本(默认)
28
- * const heatmap = new Heatmap({ id: 'canvas', colors, range });
29
- *
30
- * // Canvas 2D 版本
31
- * const heatmap = new Heatmap({ id: 'canvas', colors, range, renderer: RendererType.Canvas });
32
- */
21
+ /** 热力图统一入口,支持函数调用和构造调用。 */
33
22
  declare const Heatmap: HeatmapConstructor;
34
23
  export default Heatmap;
35
24
  export { HeatmapCanvas };
@@ -1,8 +1,10 @@
1
1
  import Engine from '../../core/Engine';
2
2
  import type { HeatmapState, InitProps, Range } from '../../types';
3
+ /** 基于二维画布完整矩阵刷新的热力图渲染器。 */
3
4
  export default class HeatmapCanvas extends Engine<HeatmapState> {
4
5
  init(props: InitProps): void;
5
6
  updateProps(e: Partial<HeatmapState>): void;
7
+ /** 按当前画布尺寸重建复用的像素缓冲区。 */
6
8
  clearImageData(): void;
7
9
  clearRect(): void;
8
10
  clear(): void;
@@ -1,4 +1,5 @@
1
- export { default as Fluorescence } from './Fluorescence';
1
+ export type { FluorescenceRendererState, IFluorescence } from './Fluorescence';
2
+ export { createFluorescence, default as Fluorescence, FluorescenceCanvas } from './Fluorescence';
2
3
  export { default as Gauge } from './Gauge';
3
4
  export type { IHeatmap } from './Heatmap';
4
5
  export { createHeatmap, default as Heatmap, HeatmapCanvas } from './Heatmap';
@@ -0,0 +1,18 @@
1
+ import type { ColorValue, RGBA } from '../../types';
2
+ export declare const FLUORESCENCE_COLOR_STEPS = 256;
3
+ export declare const GAUSSIAN_EDGE_EXPONENT = 4.5;
4
+ export declare const GAUSSIAN_EDGE_WEIGHT: number;
5
+ export declare const GAUSSIAN_INTENSITY_GAIN = 1.3;
6
+ export declare const GAUSSIAN_WEIGHT_SCALE: number;
7
+ export interface FluorescenceGaussianMetrics {
8
+ cellWidth: number;
9
+ radiusX: number;
10
+ radiusY: number;
11
+ rangeSpan: number;
12
+ xSampleCount: number;
13
+ ySampleCount: number;
14
+ }
15
+ export declare const getFluorescenceGaussianMetrics: (dataLength: number, width: number, height: number, rangeMin: number, rangeMax: number) => FluorescenceGaussianMetrics;
16
+ export declare const interpolateFluorescenceColor: (colors: readonly ColorValue[], ratio: number) => RGBA;
17
+ export declare const createFluorescenceColorLookup: (colors: readonly ColorValue[]) => Uint8Array;
18
+ export declare const getVisibleFluorescenceMaxCount: (data: Uint32Array, dataLength: number, rangeMin: number, rangeMax: number) => number;
@@ -0,0 +1,78 @@
1
+ import WebGLEngine, { type WebGLBaseState } from '../../core/WebGLEngine';
2
+ import type { ColorValue, FluorescenceRenderData, FluorescenceRendererState, InitProps, Range } from '../../types';
3
+ import { FluorescenceRenderMode } from '../../types';
4
+ interface HorizontalLocations {
5
+ position: number;
6
+ countTexture: WebGLUniformLocation;
7
+ dataWidth: WebGLUniformLocation;
8
+ cellWidth: WebGLUniformLocation;
9
+ radiusX: WebGLUniformLocation;
10
+ invLogMaxCount: WebGLUniformLocation;
11
+ }
12
+ interface GaussianLocations {
13
+ position: number;
14
+ horizontalTexture: WebGLUniformLocation;
15
+ colorTexture: WebGLUniformLocation;
16
+ canvasWidth: WebGLUniformLocation;
17
+ canvasHeight: WebGLUniformLocation;
18
+ rangeMin: WebGLUniformLocation;
19
+ rangeMax: WebGLUniformLocation;
20
+ radiusY: WebGLUniformLocation;
21
+ intensityGain: WebGLUniformLocation;
22
+ }
23
+ interface GridLocations {
24
+ position: number;
25
+ countTexture: WebGLUniformLocation;
26
+ colorTexture: WebGLUniformLocation;
27
+ dataWidth: WebGLUniformLocation;
28
+ canvasWidth: WebGLUniformLocation;
29
+ canvasHeight: WebGLUniformLocation;
30
+ rangeMin: WebGLUniformLocation;
31
+ rangeMax: WebGLUniformLocation;
32
+ invLogMaxCount: WebGLUniformLocation;
33
+ }
34
+ export interface FluorescenceWebGLState extends WebGLBaseState {
35
+ colors: ColorValue[];
36
+ data: Uint32Array;
37
+ fluorescenceMaxCount: number;
38
+ display?: boolean;
39
+ renderMode: FluorescenceRenderMode;
40
+ maxTextureSize: number;
41
+ quadBuffer: WebGLBuffer;
42
+ countTexture: WebGLTexture;
43
+ colorTexture: WebGLTexture;
44
+ horizontalTexture: WebGLTexture;
45
+ horizontalFramebuffer: WebGLFramebuffer;
46
+ horizontalProgram: WebGLProgram;
47
+ gaussianProgram: WebGLProgram;
48
+ gridProgram: WebGLProgram;
49
+ horizontalLocations: HorizontalLocations;
50
+ gaussianLocations: GaussianLocations;
51
+ gridLocations: GridLocations;
52
+ }
53
+ type FallbackHandler = (error: unknown) => void;
54
+ export default class FluorescenceWebGL extends WebGLEngine<FluorescenceWebGLState> {
55
+ private fallbackHandler?;
56
+ private disposed;
57
+ init(props: InitProps): void;
58
+ setFallbackHandler(handler: FallbackHandler): void;
59
+ private handleContextLost;
60
+ private requestFallback;
61
+ private updateColorTexture;
62
+ private rebuildHorizontalTarget;
63
+ private uploadCounts;
64
+ private bindQuad;
65
+ private drawGaussian;
66
+ private drawGrid;
67
+ updateProps(e: Partial<FluorescenceRendererState>): void;
68
+ setRange(range: Range): void;
69
+ setRenderMode(mode: FluorescenceRenderMode): void;
70
+ clear(): void;
71
+ render(data: FluorescenceRenderData): void;
72
+ draw(): void;
73
+ resize(draw?: boolean): void;
74
+ /** 同步重绘并导出与页面显示方向一致的当前帧。 */
75
+ toDataURL(type?: string, quality?: number): string | null;
76
+ dispose(): void;
77
+ }
78
+ export {};
@@ -1,17 +1,18 @@
1
1
  import { ColorInterpolator } from '../../color';
2
2
  import WebGLEngine, { type WebGLBaseState } from '../../core/WebGLEngine';
3
3
  import type { ColorValue, InitProps, Range } from '../../types';
4
- /** HeatmapWebGL 状态 */
4
+ type FallbackHandler = (error: unknown) => void;
5
+ /** WebGL 热力图运行状态。 */
5
6
  export interface HeatmapWebGLState extends WebGLBaseState {
6
7
  data: number[][];
7
8
  colors: ColorValue[];
8
9
  program: WebGLProgram | null;
9
10
  quadBuffer: WebGLBuffer | null;
10
- /** 兼容字段:单纹理模式下的数据纹理(等同于 dataTextures[0]) */
11
+ /** 兼容单纹理消费者,始终指向第一个数据纹理。 */
11
12
  dataTexture: WebGLTexture | null;
12
- /** 数据纹理分片(用于超过 MAX_TEXTURE_SIZE 的情况) */
13
+ /** 按设备纹理尺寸上限拆分的数据纹理。 */
13
14
  dataTextures: WebGLTexture[];
14
- /** 数据分片信息(与 dataTextures 下标一一对应) */
15
+ /** 与数据纹理按下标对应的分片范围。 */
15
16
  dataTiles: Array<{
16
17
  startCol: number;
17
18
  startRow: number;
@@ -23,23 +24,64 @@ export interface HeatmapWebGLState extends WebGLBaseState {
23
24
  dataHeight: number;
24
25
  CI: ColorInterpolator | null;
25
26
  maxTextureSize: number;
27
+ /** 是否按固定容量环形纹理绘制。 */
28
+ ringMode: boolean;
29
+ /** 环形纹理可容纳的总行数。 */
30
+ ringCapacity: number;
31
+ /** 最旧有效行所在的物理纹理行。 */
32
+ ringHead: number;
33
+ /** 当前已经写入的有效行数。 */
34
+ ringCount: number;
26
35
  }
27
36
  export default class HeatmapWebGL extends WebGLEngine<HeatmapWebGLState> {
37
+ private disposed;
38
+ private fallbackHandler?;
39
+ /** 非类型化输入行上传时复用,避免按分片重复分配。 */
40
+ private ringStaging;
41
+ /** 首次绘制时建立,避免每帧重复查询着色程序位置。 */
42
+ private programLocations;
43
+ /** 已安排但尚未执行的画面刷新。 */
44
+ private drawFrame;
45
+ /** 合并同一显示帧内的多次增量上传。 */
46
+ private scheduleDraw;
47
+ private drawSafely;
48
+ /** 取消尚未执行的合并绘制。 */
49
+ private cancelScheduledDraw;
50
+ /** 解析当前着色程序使用的属性和统一变量位置。 */
51
+ private resolveProgramLocations;
28
52
  init(props: InitProps): void;
29
- /** 更新颜色查找纹理 */
53
+ setFallbackHandler(handler: FallbackHandler): void;
54
+ private handleContextLost;
55
+ private requestFallback;
56
+ /** 生成并上传固定大小的颜色查找纹理。 */
30
57
  private updateColorTexture;
31
- /** 更新数据纹理 */
58
+ /** 将完整矩阵转换为浮点纹理,并按设备上限进行横纵分片。 */
32
59
  private updateDataTexture;
60
+ /**
61
+ * 准备固定高度的环形纹理,并按宽度横向分片。
62
+ * 高度超过设备纹理上限时返回失败,由调用方退回完整矩阵刷新。
63
+ */
64
+ private ensureRingLayout;
65
+ /** 将一帧按横向分片写入指定的物理纹理行。 */
66
+ private uploadRingRow;
67
+ /** 用完整矩阵重建环形纹理,供回放和重新采样使用。 */
68
+ private uploadRingReplace;
69
+ /** 用完整矩阵替换增量纹理;不满足环形布局时降级为完整刷新。 */
70
+ replace(frames: readonly ArrayLike<number>[], capacity?: number, width?: number): void;
71
+ /** 追加单帧;容量写满后覆盖最旧行并推进头指针。 */
72
+ append(frame: ArrayLike<number>, capacity?: number): void;
33
73
  updateProps(e: Partial<HeatmapWebGLState>): void;
34
74
  setRange(range: Range): void;
35
75
  clear(): void;
36
76
  render(data: number[][]): void;
77
+ /** 绘制当前完整纹理或环形纹理状态。 */
37
78
  draw(): void;
38
79
  resize(): void;
39
80
  /**
40
- * 供截图使用:重绘当前帧并读回为图片,返回视觉正确方向(已翻转 scaleY(-1))的 dataURL。
41
- * preserveDrawingBuffer: false 下,同一 JS task 内 draw() 后立即读回必读到。
81
+ * 重绘并读取当前帧,返回与页面显示方向一致的图片。
82
+ * 未保留绘图缓冲时,绘制与读取必须在同一任务内完成。
42
83
  */
43
84
  toDataURL(type?: string, quality?: number): string | null;
44
85
  dispose(): void;
45
86
  }
87
+ export {};
@@ -1,2 +1,4 @@
1
+ export type { FluorescenceWebGLState } from './FluorescenceWebGL';
2
+ export { default as FluorescenceWebGL } from './FluorescenceWebGL';
1
3
  export type { HeatmapWebGLState } from './HeatmapWebGL';
2
4
  export { default as HeatmapWebGL } from './HeatmapWebGL';
package/types/index.d.ts CHANGED
@@ -2,4 +2,4 @@ export type { ColorValue, Point, Range, RangeWithSpan, RGBA } from './common';
2
2
  export { DEFAULTS, EYE_DEFAULTS, FLUORESCENCE } from './common';
3
3
  export type { AxisYRange, CircularData, CircularPolygonItem, CircularSeriesItem, FluorescenceRenderData, GaugeData, IQData, IQEyeRenderMode, SeriesConfig } from './data';
4
4
  export { FluorescenceRenderMode, GraphicType, OrientationType, RendererType } from './enums';
5
- export type { BaseState, CircularState, FluorescenceState, GaugeDataType, GaugeState, HeatmapState, InitProps, IQEyeState, IQState, SeriesState, StateProps } from './state';
5
+ export type { BaseState, CircularState, FluorescenceRendererState, FluorescenceState, GaugeDataType, GaugeState, HeatmapState, InitProps, IQEyeState, IQState, SeriesState, StateProps } from './state';
package/types/state.d.ts CHANGED
@@ -61,6 +61,8 @@ export interface FluorescenceState extends BaseState {
61
61
  display?: boolean;
62
62
  renderMode?: FluorescenceRenderMode;
63
63
  }
64
+ /** Canvas 与 WebGL 荧光谱统一入口都能真实提供的运行状态。 */
65
+ export type FluorescenceRendererState = Omit<FluorescenceState, 'ctx' | 'imageData'>;
64
66
  /** Circular (Dial/Radar) 状态 */
65
67
  export interface CircularState extends BaseState {
66
68
  data: CircularData;
@@ -1,12 +1,6 @@
1
1
  import type { RGBA } from '../types';
2
2
  /**
3
- * 根据给定的数据数组填充图像数据
4
- * 性能优化:cellWidth 只计算一次,直接操作像素数组
5
- *
6
- * @param canvasWidth 画布宽度
7
- * @param canvasHeight 画布高度
8
- * @param data 数据数组
9
- * @param imageData 图像数据对象
10
- * @param getColor 颜色获取函数
3
+ * 将热力数据映射到复用的像素缓冲区。
4
+ * 数据行允许为空或长度不足,未覆盖区域保持透明。
11
5
  */
12
6
  export declare function fillImageData(canvasWidth: number, canvasHeight: number, data: number[][], imageData: Uint8ClampedArray, getColor: (value: number) => RGBA): void;