chartai 0.1.0 → 1.0.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/dist/chart-library.d.ts +33 -146
- package/dist/chart-library.d.ts.map +1 -1
- package/dist/chart-library.js +378 -321
- package/dist/chart-library.min.js +1 -1
- package/dist/charts/area.d.ts +6 -0
- package/dist/charts/area.d.ts.map +1 -0
- package/dist/charts/area.js +65 -0
- package/dist/charts/area.min.js +1 -0
- package/dist/charts/bar.d.ts +11 -0
- package/dist/charts/bar.d.ts.map +1 -0
- package/dist/charts/bar.js +65 -0
- package/dist/charts/bar.min.js +1 -0
- package/dist/charts/boids.js +167 -0
- package/dist/charts/boids.min.js +18 -0
- package/dist/charts/candlestick.d.ts +21 -0
- package/dist/charts/candlestick.d.ts.map +1 -0
- package/dist/charts/candlestick.js +132 -0
- package/dist/charts/candlestick.min.js +32 -0
- package/dist/charts/line.d.ts +12 -0
- package/dist/charts/line.d.ts.map +1 -0
- package/dist/charts/line.js +62 -0
- package/dist/charts/line.min.js +1 -0
- package/dist/charts/scatter.d.ts +11 -0
- package/dist/charts/scatter.d.ts.map +1 -0
- package/dist/charts/scatter.js +46 -0
- package/dist/charts/scatter.min.js +1 -0
- package/dist/chunk-0jepamv9.js +7 -0
- package/dist/chunk-1p45ex5n.min.js +2 -0
- package/dist/chunk-831dem4f.js +4 -0
- package/dist/chunk-93yrr7er.js +35 -0
- package/dist/chunk-94kc81rr.min.js +2 -0
- package/dist/chunk-a27be8p9.js +105 -0
- package/dist/chunk-bfyv7z27.min.js +2 -0
- package/dist/chunk-dmaxrg6s.min.js +2 -0
- package/dist/chunk-e7d3zgw5.min.js +2 -0
- package/dist/chunk-g6m56ptf.js +609 -0
- package/dist/chunk-m17t3vjq.js +9 -0
- package/dist/chunk-me3qaz3m.min.js +2 -0
- package/dist/chunk-qr6mweck.min.js +2 -0
- package/dist/chunk-yabjrff2.js +11 -0
- package/dist/gpu-worker.js +625 -686
- package/dist/gpu-worker.min.js +1 -1
- package/dist/msg.d.ts +33 -0
- package/dist/msg.d.ts.map +1 -0
- package/dist/plugins/coords.d.ts +18 -0
- package/dist/plugins/coords.d.ts.map +1 -0
- package/dist/plugins/hover.d.ts +15 -2
- package/dist/plugins/hover.d.ts.map +1 -1
- package/dist/plugins/hover.js +92 -13
- package/dist/plugins/hover.min.js +1 -1
- package/dist/plugins/labels-panel.d.ts +4 -0
- package/dist/plugins/labels-panel.d.ts.map +1 -0
- package/dist/plugins/labels-panel.js +122 -0
- package/dist/plugins/labels-panel.min.js +1 -0
- package/dist/plugins/labels.d.ts +17 -2
- package/dist/plugins/labels.d.ts.map +1 -1
- package/dist/plugins/labels.js +11 -99
- package/dist/plugins/labels.min.js +1 -1
- package/dist/plugins/legend.d.ts +16 -0
- package/dist/plugins/legend.d.ts.map +1 -0
- package/dist/plugins/legend.js +282 -0
- package/dist/plugins/legend.min.js +21 -0
- package/dist/plugins/shared.d.ts +7 -0
- package/dist/plugins/shared.d.ts.map +1 -0
- package/dist/plugins/zoom.d.ts +10 -2
- package/dist/plugins/zoom.d.ts.map +1 -1
- package/dist/plugins/zoom.js +63 -62
- package/dist/plugins/zoom.min.js +1 -1
- package/dist/types.d.ts +179 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +0 -0
- package/dist/types.min.js +0 -0
- package/dist/worker-inline.d.ts +1 -1
- package/dist/worker-inline.d.ts.map +1 -1
- package/package.json +11 -11
- package/readme.md +51 -42
- package/dist/chunk-bgfkgcmg.js +0 -25
- package/dist/chunk-cj3zanvs.min.js +0 -2
package/dist/chart-library.js
CHANGED
|
@@ -14,46 +14,139 @@ var exports_worker_inline = {};
|
|
|
14
14
|
__export(exports_worker_inline, {
|
|
15
15
|
WORKER_CODE: () => WORKER_CODE
|
|
16
16
|
});
|
|
17
|
-
var WORKER_CODE = 'var m=256,X="struct Uniforms{width: f32,height: f32,viewMinX: f32,viewMaxX: f32,viewMinY: f32,viewMaxY: f32,pointCount: u32,isDark: f32,bgR: f32,bgG: f32,bgB: f32,pointRadius: f32,dataMinY: f32,dataMaxY: f32,dataMinX: f32,dataMaxX: f32,visibleStart: u32,visibleCount: u32,dispatchXCount: u32,maxSamplesPerPixel: u32,seriesCount: u32,_pad2: u32,_pad3: u32,_pad4: u32,};struct SeriesInfo{color: vec4f,visibleRange: vec2u,pointSize: f32,_pad: f32,};struct SeriesIndex{index: u32,_pad0: u32,_pad1: u32,_pad2: u32,};",w="fn lowerBound(val: f32,count: u32)-> u32{var lo = 0u;var hi = count;while(lo < hi){let mid =(lo + hi)/ 2u;if(dataX[mid] < val){lo = mid + 1u;}else{hi = mid;}}return lo;}";var C="struct Uniforms{width: f32,height: f32,viewMinX: f32,viewMaxX: f32,viewMinY: f32,viewMaxY: f32,pointCount: u32,isDark: f32,bgR: f32,bgG: f32,bgB: f32,pointRadius: f32,dataMinY: f32,dataMaxY: f32,dataMinX: f32,dataMaxX: f32,visibleStart: u32,visibleCount: u32,dispatchXCount: u32,maxSamplesPerPixel: u32,seriesCount: u32,_pad2: u32,_pad3: u32,_pad4: u32,};struct SeriesInfo{color: vec4f,visibleRange: vec2u,pointSize: f32,_pad: f32,};struct SeriesIndex{index: u32,_pad0: u32,_pad1: u32,_pad2: u32,};fn l(c:vec4f)->f32{return dot(c.rgb,vec3f(.299,.587,.114))+c.a*.25;}fn fxaa(u:vec2f,t:texture_2d<f32>,s:sampler)->vec4f{let r=1./vec2f(textureDimensions(t));let rM=textureSampleLevel(t,s,u,0.);let rN=textureSampleLevel(t,s,u+vec2f(0.,-r.y),0.);let rS=textureSampleLevel(t,s,u+vec2f(0.,r.y),0.);let rE=textureSampleLevel(t,s,u+vec2f(r.x,0.),0.);let rW=textureSampleLevel(t,s,u+vec2f(-r.x,0.),0.);let lM=l(rM);let lN=l(rN);let lS=l(rS);let lE=l(rE);let lW=l(rW);let mi=min(lM,min(min(lN,lS),min(lE,lW)));let ma=max(lM,max(max(lN,lS),max(lE,lW)));let ra=ma-mi;if(ra<max(.0833,ma*.166)){return rM;}let lNW=l(textureSampleLevel(t,s,u+vec2f(-r.x,-r.y),0.));let lNE=l(textureSampleLevel(t,s,u+vec2f(r.x,-r.y),0.));let lSW=l(textureSampleLevel(t,s,u+vec2f(-r.x,r.y),0.));let lSE=l(textureSampleLevel(t,s,u+vec2f(r.x,r.y),0.));let sB=min(.35,max(0.,abs((lN+lS+lE+lW)*.25-lM)/ra-.25)*1.33);let iH=abs(lNW+lNE-2.*lN)+abs(lW+lE-2.*lM)*2.+abs(lSW+lSE-2.*lS)>=abs(lNW+lSW-2.*lW)+abs(lN+lS-2.*lM)*2.+abs(lNE+lSE-2.*lE);let l1=select(lW,lN,iH);let l2=select(lE,lS,iH);let pD=abs(l2-lM)>abs(l1-lM);let sL=select(r.x,r.y,iH);let lA=.5*(select(l1,l2,pD)+lM);let gS=max(abs(l1-lM),abs(l2-lM))*.25;var eU=u;if(iH){eU.y+=select(-.5,.5,pD)*sL;}else{eU.x+=select(-.5,.5,pD)*sL;}let eS=select(vec2f(r.x,0.),vec2f(0.,r.y),iH);var uN=eU-eS;var uP=eU+eS;var eN=l(textureSampleLevel(t,s,uN,0.))-lA;var eP=l(textureSampleLevel(t,s,uP,0.))-lA;var dN=abs(eN)>=gS;var dP=abs(eP)>=gS;for(var i=1;i<8;i++){if(!dN){uN-=eS*1.5;eN=l(textureSampleLevel(t,s,uN,0.))-lA;dN=abs(eN)>=gS;}if(!dP){uP+=eS*1.5;eP=l(textureSampleLevel(t,s,uP,0.))-lA;dP=abs(eP)>=gS;}if(dN&&dP){break;}}let dtN=select(u.x-uN.x,u.y-uN.y,iH);let dtP=select(uP.x-u.x,uP.y-u.y,iH);let eB=select(0.,.5-min(dtN,dtP)/(dtN+dtP),(lM-lA<0.)!=(select(eP<0.,eN<0.,dtN<dtP)));var fU=u;let fL=max(eB,sB);if(iH){fU.y+=select(-1.,1.,pD)*fL*sL;}else{fU.x+=select(-1.,1.,pD)*fL*sL;}return textureSampleLevel(t,s,fU,0.);}@group(0)@binding(0)var<uniform> u: Uniforms;@group(0)@binding(1)var inputTex: texture_2d<f32>;@group(0)@binding(2)var samp: sampler;struct VertexOutput{@builtin(position)pos: vec4f,@location(0)uv: vec2f,};@vertex fn vs(@builtin(vertex_index)vi: u32)-> VertexOutput{var positions = array<vec2f,4>(vec2f(-1.0,-1.0),vec2f(1.0,-1.0),vec2f(-1.0,1.0),vec2f(1.0,1.0));var uvs = array<vec2f,4>(vec2f(0.0,1.0),vec2f(1.0,1.0),vec2f(0.0,0.0),vec2f(1.0,0.0));var out: VertexOutput;out.pos = vec4f(positions[vi],0.0,1.0);out.uv = uvs[vi];return out;}@fragment fn fs(in: VertexOutput)-> @location(0)vec4f{return fxaa(in.uv,inputTex,samp);}";var y=`${X}struct LineData{screenX: f32,minScreenY: f32,maxScreenY: f32,valid: f32,};@group(0)@binding(0)var<uniform> u: Uniforms;@group(0)@binding(1)var<storage,read> dataX: array<f32>;@group(0)@binding(2)var<storage,read> dataY: array<f32>;@group(0)@binding(3)var<storage,read_write> lineData: array<LineData>;@group(0)@binding(4)var<storage,read> allSeries: array<SeriesInfo>;${w}@compute @workgroup_size(${m})fn main(@builtin(global_invocation_id)id: vec3u){let outputIdx = id.x;let maxCols = u32(u.width);let count = u.pointCount;if(outputIdx >= maxCols || count == 0u){if(outputIdx < maxCols){lineData[outputIdx] = LineData(-1.0,-1.0,-1.0,0.0);}return;}let viewRangeX = u.viewMaxX - u.viewMinX;let viewRangeY = u.viewMaxY - u.viewMinY;if(viewRangeX < 0.0001 || viewRangeY < 0.0001){lineData[outputIdx] = LineData(-1.0,-1.0,-1.0,0.0);return;}let relPx = f32(outputIdx);let pixelMinX = u.viewMinX +(relPx / u.width)* viewRangeX;let pixelMaxX = u.viewMinX +((relPx + 1.0)/ u.width)* viewRangeX;let startIdx = lowerBound(pixelMinX,count);var endIdx = lowerBound(pixelMaxX,count);endIdx = min(endIdx,count);let centerX =(pixelMinX + pixelMaxX)* 0.5;if(startIdx >= endIdx){var bestIdx = startIdx;if(startIdx > 0u && startIdx < count){let distPrev = abs(dataX[startIdx - 1u] - centerX);let distCurr = abs(dataX[startIdx] - centerX);if(distPrev < distCurr){bestIdx = startIdx - 1u;}}else if(startIdx >= count && count > 0u){bestIdx = count - 1u;}if(bestIdx >= count){lineData[outputIdx] = LineData(-1.0,-1.0,-1.0,0.0);return;}let y = dataY[bestIdx];let normY =(y - u.viewMinY)/ viewRangeY;let screenY = 1.0 - normY;let normX =(dataX[bestIdx] - u.viewMinX)/ viewRangeX;let screenX = normX;lineData[outputIdx] = LineData(screenX,screenY,screenY,1.0);return;}var dataMinY = dataY[startIdx];var dataMaxY = dataY[startIdx];let rangeCount = endIdx - startIdx;let maxSamples = u.maxSamplesPerPixel;if(maxSamples > 1u && rangeCount > maxSamples){let stride = f32(rangeCount - 1u)/ f32(maxSamples - 1u);for(var s = 0u;s < maxSamples;s++){let idx = startIdx + u32(f32(s)* stride);if(idx < endIdx){let y = dataY[idx];dataMinY = min(dataMinY,y);dataMaxY = max(dataMaxY,y);}}let lastY = dataY[endIdx - 1u];dataMinY = min(dataMinY,lastY);dataMaxY = max(dataMaxY,lastY);}else{for(var i = startIdx + 1u;i < endIdx;i++){let y = dataY[i];dataMinY = min(dataMinY,y);dataMaxY = max(dataMaxY,y);}}let normX =(centerX - u.viewMinX)/ viewRangeX;let screenX = normX;let normMaxY =(dataMaxY - u.viewMinY)/ viewRangeY;let normMinY =(dataMinY - u.viewMinY)/ viewRangeY;let minScreenY = 1.0 - normMaxY;let maxScreenY = 1.0 - normMinY;lineData[outputIdx] = LineData(screenX,minScreenY,maxScreenY,1.0);}`,L=`${X}struct LineData{screenX: f32,minScreenY: f32,maxScreenY: f32,valid: f32,};@group(0)@binding(0)var<uniform> u: Uniforms;@group(0)@binding(1)var<storage,read> lineData: array<LineData>;@group(0)@binding(2)var<storage,read> allSeries: array<SeriesInfo>;struct VertexOutput{@builtin(position)pos: vec4f,@location(0)alpha: f32,@location(1)@interpolate(flat)seriesIdx: u32,};@vertex fn vs(@builtin(vertex_index)vi: u32,@builtin(instance_index)series_idx: u32)-> VertexOutput{var out: VertexOutput;out.seriesIdx = series_idx;let maxCols = u32(u.width);let segIdx = vi / 2u;let endpoint = vi % 2u;if(segIdx < maxCols){let d = lineData[segIdx];let y = select(d.maxScreenY,d.minScreenY,endpoint == 0u);out.pos = vec4f(d.screenX * 2.0 - 1.0,1.0 - y * 2.0,0.0,d.valid);out.alpha = d.valid;}else{let connIdx = segIdx - maxCols;if(connIdx + 1u >= maxCols){out.pos = vec4f(0.0,0.0,0.0,0.0);out.alpha = 0.0;return out;}let d0 = lineData[connIdx];let d1 = lineData[connIdx + 1u];let segValid = min(d0.valid,d1.valid);if(endpoint == 0u){let midY =(d0.minScreenY + d0.maxScreenY)* 0.5;out.pos = vec4f(d0.screenX * 2.0 - 1.0,1.0 - midY * 2.0,0.0,segValid);}else{let midY =(d1.minScreenY + d1.maxScreenY)* 0.5;out.pos = vec4f(d1.screenX * 2.0 - 1.0,1.0 - midY * 2.0,0.0,segValid);}out.alpha = segValid;}return out;}@fragment fn fs(in: VertexOutput)-> @location(0)vec4f{if(in.alpha < 0.1){discard;}let series = allSeries[in.seriesIdx];return vec4f(series.color.rgb,1.0);}`;var D=`${X}@group(0)@binding(0)var<uniform> u: Uniforms;@group(0)@binding(1)var<storage,read> dataX: array<f32>;@group(0)@binding(2)var<storage,read> dataY: array<f32>;@group(0)@binding(3)var outputTex: texture_storage_2d<rgba8unorm,write>;@group(0)@binding(4)var<storage,read> allSeries: array<SeriesInfo>;@group(0)@binding(5)var<uniform> seriesIdx: SeriesIndex;@compute @workgroup_size(${m})fn main(@builtin(global_invocation_id)id: vec3u){let series = allSeries[seriesIdx.index];let visStart = series.visibleRange.x;let visCount = series.visibleRange.y;let localIdx = id.y * u.dispatchXCount + id.x;if(localIdx >= visCount){return;}let idx = visStart + localIdx;let count = u.pointCount;if(idx >= count){return;}let x = dataX[idx];let y = dataY[idx];if(y < u.viewMinY || y > u.viewMaxY){return;}let width = u32(u.width);let height = u32(u.height);let rangeX = u.viewMaxX - u.viewMinX;let rangeY = u.viewMaxY - u.viewMinY;if(rangeX < 0.0001 || rangeY < 0.0001){return;}let normX =(x - u.viewMinX)/ rangeX;let normY =(y - u.viewMinY)/ rangeY;let screenX = normX;let screenY = 1.0 - normY;let pixelX = i32(screenX * f32(width));let pixelY = i32(screenY * f32(height));if(idx > visStart){let prevX = dataX[idx - 1u];let prevY = dataY[idx - 1u];let prevNormX =(prevX - u.viewMinX)/ rangeX;let prevNormY =(prevY - u.viewMinY)/ rangeY;let prevPx = i32(prevNormX * f32(width));let prevPy = i32((1.0 - prevNormY)* f32(height));if(pixelX == prevPx && pixelY == prevPy){return;}}let iWidth = i32(width);let iHeight = i32(height);if(pixelX < 0 || pixelX >= iWidth){return;}if(pixelY < 0 || pixelY >= iHeight){return;}let color = series.color;let radius = i32(series.pointSize);for(var dy = -radius;dy <= radius;dy++){for(var dx = -radius;dx <= radius;dx++){if(dx * dx + dy * dy > radius * radius){continue;}let px = pixelX + dx;let py = pixelY + dy;if(px >= 0 && px < iWidth && py >= 0 && py < iHeight){textureStore(outputTex,vec2i(px,py),color);}}}}`;var H=`${X}struct BarData{screenX: f32,minY: f32,maxY: f32,barWidth: f32,};@group(0)@binding(0)var<uniform> u: Uniforms;@group(0)@binding(1)var<storage,read> dataX: array<f32>;@group(0)@binding(2)var<storage,read> dataY: array<f32>;@group(0)@binding(3)var<storage,read_write> barData: array<BarData>;@group(0)@binding(4)var<storage,read> allSeries: array<SeriesInfo>;@group(0)@binding(5)var<uniform> seriesIdx: SeriesIndex;${w}fn barHalfWidth(idx: u32,count: u32)-> f32{if(count <= 1u){return(u.viewMaxX - u.viewMinX)* 0.4;}var spacing: f32;if(idx == 0u){spacing = dataX[1u] - dataX[0u];}else if(idx >= count - 1u){spacing = dataX[count - 1u] - dataX[count - 2u];}else{spacing = min(dataX[idx + 1u] - dataX[idx],dataX[idx] - dataX[idx - 1u]);}let seriesCount = max(1u,u.seriesCount);return(spacing * 0.4)/ f32(seriesCount);}@compute @workgroup_size(${m})fn main(@builtin(global_invocation_id)id: vec3u){let outputIdx = id.x;let maxCols = u32(u.width);let count = u.pointCount;if(outputIdx >= maxCols || count == 0u){if(outputIdx < maxCols){barData[outputIdx] = BarData(0.0,0.0,0.0,0.0);}return;}let viewRangeX = u.viewMaxX - u.viewMinX;let viewRangeY = u.viewMaxY - u.viewMinY;if(viewRangeX < 0.0001 || viewRangeY < 0.0001){barData[outputIdx] = BarData(0.0,0.0,0.0,0.0);return;}let relPx = f32(outputIdx);let pixelMinX = u.viewMinX +(relPx / u.width)* viewRangeX;let pixelMaxX = u.viewMinX +((relPx + 1.0)/ u.width)* viewRangeX;let startIdx = lowerBound(pixelMinX,count);var endIdx = lowerBound(pixelMaxX,count);endIdx = min(endIdx,count);let centerX =(pixelMinX + pixelMaxX)* 0.5;let onePixel = 1.0 / u.width;if(startIdx >= endIdx){var hit = false;var bestX: f32 = 0.0;var bestY: f32 = 0.0;var bestHW: f32 = 0.0;var bestDist: f32 = 1e10;if(startIdx < count){let bx = dataX[startIdx];let hw = barHalfWidth(startIdx,count);if(pixelMinX < bx + hw && pixelMaxX > bx - hw){let d = abs(bx - centerX);bestX = bx;bestY = dataY[startIdx];bestHW = hw;bestDist = d;hit = true;}}if(startIdx > 0u){let prev = startIdx - 1u;let bx = dataX[prev];let hw = barHalfWidth(prev,count);if(pixelMinX < bx + hw && pixelMaxX > bx - hw){let d = abs(bx - centerX);if(d < bestDist){bestX = bx;bestY = dataY[prev];bestHW = hw;bestDist = d;}hit = true;}}if(!hit){barData[outputIdx] = BarData(0.0,0.0,0.0,0.0);return;}let seriesCount = max(1u,u.seriesCount);let barOffset =(f32(seriesIdx.index)- f32(seriesCount - 1u)* 0.5)*(bestHW * 2.0);let offsetX = bestX + barOffset;let normX =(offsetX - u.viewMinX)/ viewRangeX;let fullWidth = bestHW * 2.0 / viewRangeX;let gapSize = max(onePixel,fullWidth * 0.05);let bw = max(fullWidth - gapSize,onePixel);barData[outputIdx] = BarData(normX,bestY,bestY,bw);return;}var dataMinY = dataY[startIdx];var dataMaxY = dataY[startIdx];let rangeCount = endIdx - startIdx;let maxSamples = u.maxSamplesPerPixel;if(maxSamples > 0u && rangeCount > maxSamples){let stride = f32(rangeCount - 1u)/ f32(maxSamples - 1u);for(var s = 0u;s < maxSamples;s++){let idx = startIdx + u32(f32(s)* stride);if(idx < endIdx){let y = dataY[idx];dataMinY = min(dataMinY,y);dataMaxY = max(dataMaxY,y);}}let lastY = dataY[endIdx - 1u];dataMinY = min(dataMinY,lastY);dataMaxY = max(dataMaxY,lastY);}else{for(var i = startIdx + 1u;i < endIdx;i++){let y = dataY[i];dataMinY = min(dataMinY,y);dataMaxY = max(dataMaxY,y);}}let hw = barHalfWidth(startIdx,count);let fullWidth = hw * 2.0 / viewRangeX;let gapSize = max(onePixel,fullWidth * 0.05);let bw = max(fullWidth - gapSize,onePixel);let seriesCount = max(1u,u.seriesCount);let barOffset =(f32(seriesIdx.index)- f32(seriesCount - 1u)* 0.5)*(hw * 2.0);let dataX_centered = dataX[startIdx] + barOffset;let normX =(dataX_centered - u.viewMinX)/ viewRangeX;barData[outputIdx] = BarData(normX,dataMinY,dataMaxY,bw);}`,U=`${X}struct BarData{screenX: f32,minY: f32,maxY: f32,barWidth: f32,};@group(0)@binding(0)var<uniform> u: Uniforms;@group(0)@binding(1)var<storage,read> barData: array<BarData>;@group(0)@binding(2)var<storage,read> allSeries: array<SeriesInfo>;struct VertexOutput{@builtin(position)pos: vec4f,@location(0)normY: f32,@location(1)@interpolate(flat)seriesIdx: u32,};@vertex fn vs(@builtin(vertex_index)vi: u32,@builtin(instance_index)series_idx: u32)-> VertexOutput{var out: VertexOutput;out.seriesIdx = series_idx;let maxCols = u32(u.width);let colIdx = vi / 6u;let vertexType = vi % 6u;if(colIdx >= maxCols){out.pos = vec4f(0.0,0.0,0.0,0.0);out.normY = 0.0;return out;}let bd = barData[colIdx];if(bd.barWidth <= 0.0){out.pos = vec4f(0.0,0.0,0.0,0.0);out.normY = 0.0;return out;}let viewRangeY = u.viewMaxY - u.viewMinY;let safeRangeY = select(viewRangeY,1.0,viewRangeY < 0.0001);let normMinY =(min(bd.minY,0.0)- u.viewMinY)/ safeRangeY;let normMaxY =(max(bd.maxY,0.0)- u.viewMinY)/ safeRangeY;let top = 1.0 - normMaxY;let bottom = 1.0 - normMinY;let halfW = bd.barWidth * 0.5;let left = bd.screenX - halfW;let right = bd.screenX + halfW;var positions = array<vec2f,6>(vec2f(left,bottom),vec2f(right,bottom),vec2f(left,top),vec2f(left,top),vec2f(right,bottom),vec2f(right,top));let screenPos = positions[vertexType];let clipX = screenPos.x * 2.0 - 1.0;let clipY = 1.0 - screenPos.y * 2.0;out.pos = vec4f(clipX,clipY,0.0,1.0);out.normY = normMaxY;return out;}@fragment fn fs(in: VertexOutput)-> @location(0)vec4f{let series = allSeries[in.seriesIdx];return vec4f(series.color.rgb,0.85);}`;var t,R,v=new Map,S=!1,p={},a={},N=0,P=0,_=!1,O=null,W=new ArrayBuffer(112),j=new Float32Array(W),F=new Uint32Array(W),A,q=(e,i)=>{if(!e.seriesStorageBuffer||e.series.length===0)return;let n=new Float32Array(e.series.length*8),l=new Uint32Array(n.buffer);for(let u=0;u<e.series.length;u++){let o=e.series[u],x=u*8;n[x+0]=o.colorR,n[x+1]=o.colorG,n[x+2]=o.colorB,n[x+3]=1,l[x+4]=o.visibleStart,l[x+5]=o.visibleCount,n[x+6]=i?.[u]??e.pointSize,n[x+7]=0}t.queue.writeBuffer(e.seriesStorageBuffer,0,n)},E=(e,i,n,l)=>{let u=j,o=F,x=e.maxX-e.minX,f=e.maxY-e.minY,Y=e.bgColor??(S?[0.11,0.11,0.12]:[0.98,0.98,0.98]);u.set([e.width,e.height,e.minX+e.panX*x,e.minX+e.panX*x+x/e.zoomX,e.minY+e.panY*f,e.minY+e.panY*f+f/e.zoomY],0),o[6]=i.pointCount,u.set([S?1:0,...Y,l??e.pointSize,e.minY,e.maxY,e.minX,e.maxX],7),o[16]=i.visibleStart,o[17]=i.visibleCount,o[18]=0,o[19]=e.maxSamplesPerPixel,o[20]=e.series.length,t.queue.writeBuffer(e.uniformBuffer,0,W)},T=(e,i,n,l,u)=>{let o=t.createShaderModule({code:i}),x=t.createShaderModule({code:n});a[`${e}Compute`]=t.createComputePipeline({layout:t.createPipelineLayout({bindGroupLayouts:[p[`${e}Compute`]]}),compute:{module:o,entryPoint:"main"}}),a[`${e}Render`]=t.createRenderPipeline({layout:t.createPipelineLayout({bindGroupLayouts:[p[`${e}Render`]]}),vertex:{module:x,entryPoint:"vs"},fragment:{module:x,entryPoint:"fs",targets:[{format:"rgba8unorm",blend:u}]},primitive:{topology:l}})};async function J(){if(t)return!0;if(!navigator.gpu)return postMessage({type:"error",message:"WebGPU not supported"}),!1;let e=await navigator.gpu.requestAdapter();if(!e)return postMessage({type:"error",message:"No GPU adapter found"}),!1;return t=await e.requestDevice({requiredLimits:{maxBufferSize:e.limits.maxBufferSize,maxStorageBufferBindingSize:e.limits.maxStorageBufferBindingSize}}),R=navigator.gpu.getPreferredCanvasFormat(),t.lost.then((i)=>{postMessage({type:"error",message:`GPU device lost: ${i.reason} - ${i.message}`})}),K(),postMessage({type:"gpu-ready"}),!0}function K(){p.lineCompute=t.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.COMPUTE,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}},{binding:2,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}},{binding:3,visibility:GPUShaderStage.COMPUTE,buffer:{type:"storage"}},{binding:4,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}}]}),p.lineRender=t.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.VERTEX|GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.VERTEX,buffer:{type:"read-only-storage"}},{binding:2,visibility:GPUShaderStage.FRAGMENT,buffer:{type:"read-only-storage"}}]}),p.scatterCompute=t.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.COMPUTE,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}},{binding:2,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}},{binding:3,visibility:GPUShaderStage.COMPUTE,storageTexture:{access:"write-only",format:"rgba8unorm"}},{binding:4,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}},{binding:5,visibility:GPUShaderStage.COMPUTE,buffer:{type:"uniform"}}]}),p.fxaaRender=t.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.VERTEX|GPUShaderStage.FRAGMENT,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:2,visibility:GPUShaderStage.FRAGMENT,sampler:{}}]}),p.boxCompute=t.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.COMPUTE,buffer:{type:"uniform"}},{binding:1,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}},{binding:2,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}},{binding:3,visibility:GPUShaderStage.COMPUTE,buffer:{type:"storage"}},{binding:4,visibility:GPUShaderStage.COMPUTE,buffer:{type:"read-only-storage"}},{binding:5,visibility:GPUShaderStage.COMPUTE,buffer:{type:"uniform"}}]}),p.boxRender=p.lineRender,T("line",y,L,"line-list",{color:{srcFactor:"src-alpha",dstFactor:"one-minus-src-alpha"},alpha:{srcFactor:"one",dstFactor:"one-minus-src-alpha"}}),T("box",H,U,"triangle-list",{color:{srcFactor:"src-alpha",dstFactor:"one-minus-src-alpha"},alpha:{srcFactor:"one",dstFactor:"one-minus-src-alpha"}});let e=t.createShaderModule({code:D});a.scatterCompute=t.createComputePipeline({layout:t.createPipelineLayout({bindGroupLayouts:[p.scatterCompute]}),compute:{module:e,entryPoint:"main"}});let i=t.createShaderModule({code:C});a.fxaaRender=t.createRenderPipeline({layout:t.createPipelineLayout({bindGroupLayouts:[p.fxaaRender]}),vertex:{module:i,entryPoint:"vs"},fragment:{module:i,entryPoint:"fs",targets:[{format:R}]},primitive:{topology:"triangle-strip"}}),A=t.createSampler({magFilter:"linear",minFilter:"linear"})}function Q(e,i,n,l=3,u=0,o=null){let x=t?i.getContext("webgpu"):null;if(!x){postMessage({type:"error",message:`Failed to initialize WebGPU context: ${e}`});return}try{x.configure({device:t,format:R,alphaMode:"premultiplied"})}catch(M){postMessage({type:"error",message:`Failed to configure WebGPU context: ${e}`,err:M.toString()});return}let f=t.limits.maxTextureDimension2D,Y=Math.min(Math.floor(Number(i.width)||800),f),d=Math.min(Math.floor(Number(i.height)||400),f),g=t.createBuffer({size:112,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),I={id:e,canvas:i,ctx:x,type:n,visible:!0,pointSize:l,maxSamplesPerPixel:u,series:[],uniformBuffer:g,seriesStorageBuffer:null,outputTexture:null,outputTextureView:null,fxaaBindGroup:null,width:Y,height:d,panX:0,panY:0,zoomX:1,zoomY:1,minX:0,maxX:1,minY:0,maxY:1,bgColor:o,dirty:!0};try{$(I)}catch(M){postMessage({type:"error",message:`Cannot create chart ${e}: resource creation failed - ${M}`});return}v.set(e,I),postMessage({type:"chart-registered",id:e})}function $(e){if(e.outputTexture)e.outputTexture.destroy();let i=Math.max(1,e.width),n=Math.max(1,e.height),l=e.type==="scatter"?GPUTextureUsage.STORAGE_BINDING|GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.RENDER_ATTACHMENT:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.RENDER_ATTACHMENT;e.outputTexture=t.createTexture({size:[i,n],format:"rgba8unorm",usage:l}),e.outputTextureView=e.outputTexture.createView(),e.fxaaBindGroup=t.createBindGroup({layout:p.fxaaRender,entries:[{binding:0,resource:{buffer:e.uniformBuffer}},{binding:1,resource:e.outputTextureView},{binding:2,resource:A}]})}function V(e,i,n){if(!e.seriesStorageBuffer)return;if(e.type==="scatter"){if(i.seriesIndexBuffer)i.seriesIndexBuffer.destroy();i.seriesIndexBuffer=t.createBuffer({size:16,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST});let l=new Uint32Array([n,0,0,0]);t.queue.writeBuffer(i.seriesIndexBuffer,0,l),i.computeBindGroup=t.createBindGroup({layout:p.scatterCompute,entries:[{binding:0,resource:{buffer:e.uniformBuffer}},{binding:1,resource:{buffer:i.dataX}},{binding:2,resource:{buffer:i.dataY}},{binding:3,resource:e.outputTextureView},{binding:4,resource:{buffer:e.seriesStorageBuffer}},{binding:5,resource:{buffer:i.seriesIndexBuffer}}]})}else{let l=e.type==="line"?p.lineCompute:p.boxCompute,u=e.type==="line"?p.lineRender:p.boxRender;if(i.lineBuffer)i.lineBuffer.destroy();if(i.lineBuffer=t.createBuffer({size:e.width*16,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.VERTEX}),e.type==="box"){if(i.seriesIndexBuffer)i.seriesIndexBuffer.destroy();i.seriesIndexBuffer=t.createBuffer({size:16,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST});let x=new Uint32Array([n,0,0,0]);t.queue.writeBuffer(i.seriesIndexBuffer,0,x)}let o=[{binding:0,resource:{buffer:e.uniformBuffer}},{binding:1,resource:{buffer:i.dataX}},{binding:2,resource:{buffer:i.dataY}},{binding:3,resource:{buffer:i.lineBuffer}},{binding:4,resource:{buffer:e.seriesStorageBuffer}}];if(e.type==="box"&&i.seriesIndexBuffer)o.push({binding:5,resource:{buffer:i.seriesIndexBuffer}});i.computeBindGroup=t.createBindGroup({layout:l,entries:o}),i.renderBindGroup=t.createBindGroup({layout:u,entries:[{binding:0,resource:{buffer:e.uniformBuffer}},{binding:1,resource:{buffer:i.lineBuffer}},{binding:2,resource:{buffer:e.seriesStorageBuffer}}]})}}function Z(e,i,n){if(i===e.width&&n===e.height)return;if(i<=0||n<=0)return;e.width=i,e.height=n,e.canvas.width=i,e.canvas.height=n;try{$(e);for(let l=0;l<e.series.length;l++)V(e,e.series[l],l)}catch(l){postMessage({type:"error",message:`resize failed for chart ${e.id}: ${l}`})}}function G(e,i,n){let l=v.get(e);if(!l||!t){if(!l)postMessage({type:"error",message:`update-series failed: chart ${e} not found`});return}try{l.minX=n.minX,l.maxX=n.maxX,l.minY=n.minY,l.maxY=n.maxY;for(let u of l.series){if(u.dataX.destroy(),u.dataY.destroy(),u.lineBuffer)u.lineBuffer.destroy();if(u.seriesIndexBuffer)u.seriesIndexBuffer.destroy()}if(l.series=[],l.seriesStorageBuffer)l.seriesStorageBuffer.destroy();if(i.length>0)l.seriesStorageBuffer=t.createBuffer({size:Math.max(32,i.length*32),usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST});for(let u of i){let o=t.createBuffer({size:Math.max(16,u.dataX.byteLength),usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST}),x=t.createBuffer({size:Math.max(16,u.dataY.byteLength),usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST});t.queue.writeBuffer(o,0,u.dataX),t.queue.writeBuffer(x,0,u.dataY);let f={label:u.label,colorR:u.colorR,colorG:u.colorG,colorB:u.colorB,dataX:o,dataY:x,lineBuffer:null,seriesIndexBuffer:null,pointCount:u.dataX.length,visibleStart:0,visibleCount:u.dataX.length,computeBindGroup:null,renderBindGroup:null};l.series.push(f)}for(let u=0;u<l.series.length;u++)V(l,l.series[u],u);postMessage({type:"bounds-update",id:e,...n})}catch(u){postMessage({type:"error",message:`update-series failed for chart ${e}: ${u}`})}}function z(e){if(!e.ctx)return;if(e.width===0||e.height===0)return;if(e.series.length===0)return;let i;try{i=e.ctx.getCurrentTexture().createView()}catch{return}k(e,i)}function k(e,i){let n=t.createCommandEncoder(),l;if(e.type==="scatter"){let f=e.width*e.height*4;l=e.series.map((Y)=>{let d=Math.max(1,Y.visibleCount),g=Math.PI*e.pointSize*e.pointSize;if(d*g>f)return Math.max(1,Math.sqrt(f/(d*Math.PI)));return e.pointSize})}if(q(e,l),e.series.length>0)E(e,e.series[0],0);n.beginRenderPass({colorAttachments:[{view:e.outputTextureView,loadOp:"clear",storeOp:"store",clearValue:{r:0,g:0,b:0,a:0}}]}).end();for(let x=0;x<e.series.length;x++){let f=e.series[x];if(f.pointCount===0)continue;if(e.type==="scatter"){let d=Math.ceil(f.visibleCount/m),g=Math.min(d,65535),I=Math.ceil(d/65535),M=new Uint32Array([g*m]);t.queue.writeBuffer(e.uniformBuffer,72,M);let r=n.beginComputePass();r.setPipeline(a.scatterCompute),r.setBindGroup(0,f.computeBindGroup),r.dispatchWorkgroups(g,I),r.end()}else{E(e,f,x);let Y=e.type==="line"?a.lineCompute:a.boxCompute,d=n.beginComputePass();d.setPipeline(Y),d.setBindGroup(0,f.computeBindGroup),d.dispatchWorkgroups(Math.ceil(e.width/m)),d.end()}}if(e.type!=="scatter"){let x=e.type==="line"?a.lineRender:a.boxRender,f=e.type==="line"?Math.max(0,e.width*4-2):e.width*6,Y=n.beginRenderPass({colorAttachments:[{view:e.outputTextureView,loadOp:"load",storeOp:"store"}]});Y.setPipeline(x);for(let d=0;d<e.series.length;d++){let g=e.series[d];if(g.pointCount===0)continue;Y.setBindGroup(0,g.renderBindGroup),Y.draw(f,1,0,d)}Y.end()}let o=n.beginRenderPass({colorAttachments:[{view:i,loadOp:"clear",storeOp:"store",clearValue:{r:0,g:0,b:0,a:0}}]});if(o.setPipeline(a.fxaaRender),e.fxaaBindGroup)o.setBindGroup(0,e.fxaaBindGroup);o.draw(4),o.end(),t.queue.submit([n.finish()])}function s(){if(!_)_=!0,requestAnimationFrame(ee)}function b(e){if(e.dirty=!0,e.visible)s()}function B(){let e=!1;for(let i of v.values())if(i.dirty=!0,i.visible)e=!0;if(e)s()}function c(){let e=0;for(let i of v.values())if(i.visible&&i.width>0)e++;return e}function h(){if(O!==null)return;O=setInterval(()=>{postMessage({type:"stats",fps:N,renderMs:P,totalCharts:v.size,activeCharts:c()}),N=0},1000)}function ee(){_=!1;let e=performance.now();for(let i of v.values())if(i.visible&&i.dirty&&i.width>0)z(i),i.dirty=!1;P=performance.now()-e,N++}self.onmessage=async(e)=>{let{type:i,...n}=e.data;switch(i){case"init":if(S=n.isDark||!1,await J())h();break;case"theme":S=n.isDark,B();break;case"register-chart":Q(n.id,n.canvas,n.chartType||"scatter",n.pointSize??3,n.maxSamplesPerPixel??100,n.bgColor??null);{let l=v.get(n.id);if(l)b(l)}break;case"unregister-chart":{let l=v.get(n.id);if(l){try{l.ctx.unconfigure()}catch{}if(l.uniformBuffer.destroy(),l.seriesStorageBuffer)l.seriesStorageBuffer.destroy();if(l.outputTexture)l.outputTexture.destroy();for(let u of l.series){if(u.dataX.destroy(),u.dataY.destroy(),u.lineBuffer)u.lineBuffer.destroy();if(u.seriesIndexBuffer)u.seriesIndexBuffer.destroy()}v.delete(n.id)}postMessage({type:"chart-unregistered",id:n.id});break}case"set-point-size":{let l=v.get(n.id);if(l)l.pointSize=Math.max(1,Math.min(8,n.pointSize)),b(l);break}case"set-max-samples":{let l=v.get(n.id);if(l)l.maxSamplesPerPixel=Math.max(0,n.maxSamplesPerPixel|0),b(l);break}case"set-style":{let l=v.get(n.id);if(l){if(n.bgColor!==void 0)l.bgColor=n.bgColor;b(l)}break}case"update-series":{G(n.id,n.series,n.bounds);let l=v.get(n.id);if(l)b(l);break}case"set-visibility":{let l=v.get(n.id);if(l){if(l.visible=n.visible,n.visible&&l.dirty)s()}break}case"view-transform":{let l=v.get(n.id);if(l)l.panX=n.panX,l.panY=n.panY,l.zoomX=Math.max(0.1,Math.min(1e6,n.zoomX)),l.zoomY=Math.max(0.1,Math.min(1e6,n.zoomY)),b(l);break}case"resize":{let l=v.get(n.id);if(l&&n.width>0&&n.height>0)Z(l,n.width,n.height),b(l);break}case"batch-view-transform":{let l=Math.max(0.1,Math.min(1e6,n.zoomX)),u=Math.max(0.1,Math.min(1e6,n.zoomY));for(let o of n.transforms){let x=v.get(o.id);if(x)x.panX=n.panX,x.panY=n.panY,x.zoomX=l,x.zoomY=u,x.dirty=!0}s();break}case"sync-view":for(let l of v.values())l.panX=n.panX,l.panY=n.panY,l.zoomX=n.zoomX,l.zoomY=n.zoomY;B();break}};\n';
|
|
17
|
+
var WORKER_CODE = 'var k="fn luma(c:vec4f)->f32{return dot(c.rgb,vec3f(.299,.587,.114));}fn laaa(uv:vec2f,t:texture_2d<f32>,s:sampler)->vec4f{let r=1./vec2f(textureDimensions(t));let m=textureSample(t,s,uv);let n=textureSample(t,s,uv+vec2f(0.,-r.y));let e=textureSample(t,s,uv+vec2f(r.x,0.));let w=textureSample(t,s,uv+vec2f(-r.x,0.));let sv=textureSample(t,s,uv+vec2f(0.,r.y));let lm=luma(m);let ln=luma(n);let le=luma(e);let lw=luma(w);let ls=luma(sv);let lo=min(lm,min(min(ln,ls),min(le,lw)));let hi=max(lm,max(max(ln,ls),max(le,lw)));let rng=hi-lo;if(rng<max(.0833,hi*.166)){return m;}return mix(m,(m+n+e+w+sv)*.2,min(rng*3.,1.));}struct BV{@builtin(position)p:vec4f,@location(0)uv:vec2f}@group(0)@binding(0)var inputTex:texture_2d<f32>;@group(0)@binding(1)var samp:sampler;@vertex fn vs(@builtin(vertex_index)i:u32)->BV{var p=array<vec2f,4>(vec2f(-1,-1),vec2f(1,-1),vec2f(-1,1),vec2f(1,1));var u=array<vec2f,4>(vec2f(0,1),vec2f(1,1),vec2f(0,0),vec2f(1,0));return BV(vec4f(p[i],0,1),u[i]);}@fragment fn fs(v:BV)->@location(0)vec4f{return laaa(v.uv,inputTex,samp);}";var T={INIT:0,THEME:1,REGISTER_RENDERER:2,REGISTER_CHART:3,UNREGISTER_CHART:4,UPDATE_SERIES:5,RESIZE:6,VIEW_TRANSFORM:7,BATCH_VIEW_TRANSFORM:8,SET_VISIBILITY:9,SET_STYLE:10,SET_UNIFORMS:11,GPU_READY:12,ERROR:13,STATS:14},Q={NO_GPU:"e1:no-gpu",NO_ADAPTER:"e2:no-adapter",DEVICE_LOST:"e3:device-lost",NOT_READY:"e4:not-ready",COMPILE:"e5:compile",CTX_GET:"e6:ctx-get",CTX_CFG:"e7:ctx-cfg",TEX:"e8:tex",BIND_S:"e9:bind-s",BIND_C:"e10:bind-c",UPDATE:"e11:update",NO_RENDERER:"e12:no-renderer",RESIZE:"e13:resize"};var j,U,J=new Map,R=new Map,w=!1,I,B,l,P=0,M=0,C=!1,p=null,L=new ArrayBuffer(64),b=new Float32Array(L),n=new Uint32Array(L);function D(_){let O=GPUBufferUsage.COPY_DST;for(let N of _)switch(N.toUpperCase()){case"STORAGE":O|=GPUBufferUsage.STORAGE;break;case"VERTEX":O|=GPUBufferUsage.VERTEX;break;case"UNIFORM":O|=GPUBufferUsage.UNIFORM;break;case"COPY_SRC":O|=GPUBufferUsage.COPY_SRC;break;case"COPY_DST":O|=GPUBufferUsage.COPY_DST;break;case"INDEX":O|=GPUBufferUsage.INDEX;break;case"INDIRECT":O|=GPUBufferUsage.INDIRECT;break}return O}function i(_,O,N){let m=N==="compute"?GPUShaderStage.COMPUTE:GPUShaderStage.VERTEX|GPUShaderStage.FRAGMENT;if(_==="uniforms"||_==="custom-uniforms"||_==="series-index")return{visibility:m,buffer:{type:"uniform"}};if(_==="render-target"){if(O)return{visibility:GPUShaderStage.COMPUTE,storageTexture:{access:"write-only",format:"rgba8unorm"}};return{visibility:m,texture:{sampleType:"float"}}}if(O)return{visibility:m,buffer:{type:"storage"}};return{visibility:m,buffer:{type:"read-only-storage"}}}function z(_,O,N,m,W){switch(_){case"uniforms":return{buffer:N.uniformBuffer};case"custom-uniforms":return{buffer:N.customUniformBuffer};case"series-info":return{buffer:N.seriesStorageBuffer};case"render-target":return N.outputTextureView;case"x-data":return{buffer:m.dataX};case"y-data":return{buffer:m.dataY};case"series-index":return{buffer:m.seriesIndexBuffer}}if(_.endsWith("-data")){let Y=_.slice(0,-5);return{buffer:m.extraBuffers.get(Y)}}if(W.config.bufferDefs.find((Y)=>Y.name===_)?.perSeries)return{buffer:m.seriesBuffers.get(_)};return{buffer:N.chartBuffers.get(_)}}function d(_){let O=new Map,N=new Map;for(let m=0;m<_.passes.length;m++){let W=_.passes[m],X=W.bindings.map((v)=>({binding:v.binding,...i(v.source,v.write,W.type)})),Y=j.createBindGroupLayout({entries:X});N.set(`pass-${m}`,Y);let H=j.createPipelineLayout({bindGroupLayouts:[Y]}),G=j.createShaderModule({code:_.shaders[W.shader]});if(W.type==="compute")O.set(`pass-${m}`,j.createComputePipeline({layout:H,compute:{module:G,entryPoint:"main"}}));else O.set(`pass-${m}`,j.createRenderPipeline({layout:H,vertex:{module:G,entryPoint:"vs"},fragment:{module:G,entryPoint:"fs",targets:[{format:"rgba8unorm",blend:W.blend}]},primitive:{topology:W.topology??"triangle-list"}}))}return{config:_,pipelines:O,passLayouts:N}}function h(_){if(!_.seriesStorageBuffer||_.series.length===0)return;let O=new Float32Array(_.series.length*8),N=new Uint32Array(O.buffer);for(let m=0;m<_.series.length;m++){let W=_.series[m],X=m*8;O[X+0]=W.colorR,O[X+1]=W.colorG,O[X+2]=W.colorB,O[X+3]=1,N[X+4]=W.visibleStart,N[X+5]=W.visibleCount}j.queue.writeBuffer(_.seriesStorageBuffer,0,O)}function E(_,O){let N=b,m=n,W=_.maxX-_.minX,X=_.maxY-_.minY,Y=_.bgColor??(w?[0.11,0.11,0.12]:[0.98,0.98,0.98]);N[0]=_.width,N[1]=_.height,N[2]=_.minX+_.panX*W,N[3]=_.minX+_.panX*W+W/_.zoomX,N[4]=_.minY+_.panY*X,N[5]=_.minY+_.panY*X+X/_.zoomY,m[6]=O.pointCount,m[7]=_.series.length,m[8]=w?1:0,N[9]=Y[0],N[10]=Y[1],N[11]=Y[2],N[12]=_.minX,N[13]=_.maxX,N[14]=_.minY,N[15]=_.maxY,j.queue.writeBuffer(_.uniformBuffer,0,L)}function y(_,O){if(!_.customUniformBuffer||O.uniformDefs.length===0)return;let N=O.uniformDefs.length,m=Math.ceil(N*4/16)*16,W=new ArrayBuffer(m),X=new Float32Array(W),Y=new Uint32Array(W);for(let H=0;H<N;H++){let G=O.uniformDefs[H],v=_.customUniformValues[G.name]??G.default;if(G.type==="u32")Y[H]=v>>>0;else X[H]=v}j.queue.writeBuffer(_.customUniformBuffer,0,W)}function g(_,O,N){for(let[,m]of _.chartBuffers)m.destroy();_.chartBuffers.clear();for(let m of O.config.bufferDefs)if(!m.perSeries){let W=Math.max(16,N[m.name]??16);_.chartBuffers.set(m.name,j.createBuffer({size:W,usage:D(m.usages)}))}if(O.config.uniformDefs.length>0){if(_.customUniformBuffer)_.customUniformBuffer.destroy();let m=Math.max(16,Math.ceil(O.config.uniformDefs.length*4/16)*16);_.customUniformBuffer=j.createBuffer({size:m,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),y(_,O.config)}}function u(_,O,N,m){for(let[,W]of _.seriesBuffers)W.destroy();_.seriesBuffers.clear();for(let W of N.config.bufferDefs)if(W.perSeries){let X=Math.max(16,m[W.name]??16);_.seriesBuffers.set(W.name,j.createBuffer({size:X,usage:D(W.usages)}))}if(_.seriesIndexBuffer)_.seriesIndexBuffer.destroy();_.seriesIndexBuffer=j.createBuffer({size:16,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),j.queue.writeBuffer(_.seriesIndexBuffer,0,new Uint32Array([O,0,0,0]))}function f(_,O){if(!_.seriesStorageBuffer)return;for(let N=0;N<_.series.length;N++){let m=_.series[N];m.passBindGroups=[];for(let W=0;W<O.config.passes.length;W++){let X=O.config.passes[W];if(!X.perSeries){m.passBindGroups.push(null);continue}let Y=O.passLayouts.get(`pass-${W}`);try{let H=X.bindings.map((G)=>({binding:G.binding,resource:z(G.source,G.write,_,m,O)}));m.passBindGroups.push(j.createBindGroup({layout:Y,entries:H}))}catch(H){postMessage({type:T.ERROR,code:Q.BIND_S}),m.passBindGroups.push(null)}}}_.chartPassBindGroups=[];for(let N=0;N<O.config.passes.length;N++){let m=O.config.passes[N];if(m.perSeries){_.chartPassBindGroups.push(null);continue}let W=O.passLayouts.get(`pass-${N}`);try{let X=m.bindings.map((Y)=>({binding:Y.binding,resource:z(Y.source,Y.write,_,null,O)}));_.chartPassBindGroups.push(j.createBindGroup({layout:W,entries:X}))}catch(X){postMessage({type:T.ERROR,code:Q.BIND_C}),_.chartPassBindGroups.push(null)}}}function S(_){if(_.outputTexture)_.outputTexture.destroy();let O=Math.max(1,_.width),N=Math.max(1,_.height),m=R.get(_.rendererName),W=GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.RENDER_ATTACHMENT;if(m){for(let X of m.config.passes)if(X.type==="compute"){for(let Y of X.bindings)if(Y.source==="render-target"&&Y.write){W|=GPUTextureUsage.STORAGE_BINDING;break}}}_.outputTexture=j.createTexture({size:[O,N],format:"rgba8unorm",usage:W}),_.outputTextureView=_.outputTexture.createView(),_.blitBindGroup=j.createBindGroup({layout:B,entries:[{binding:0,resource:_.outputTextureView},{binding:1,resource:l}]})}function c(_){let O=R.get(_.rendererName);if(!O)return;if(!_.ctx||_.width===0||_.height===0||_.series.length===0)return;let N;try{N=_.ctx.getCurrentTexture().createView()}catch{return}let m=j.createCommandEncoder();if(h(_),_.series.length>0)E(_,_.series[0]);m.beginRenderPass({colorAttachments:[{view:_.outputTextureView,loadOp:"clear",storeOp:"store",clearValue:{r:0,g:0,b:0,a:0}}]}).end();for(let Y=0;Y<O.config.passes.length;Y++){let H=O.config.passes[Y],G=O.pipelines.get(`pass-${Y}`);if(!G)continue;if(H.type==="compute")if(H.perSeries)for(let v=0;v<_.series.length;v++){let V=_.series[v];if(V.pointCount===0)continue;E(_,V);let q=_.perSeriesPassMeta[v]?.[Y]?.dispatch??{x:1},$=V.passBindGroups[Y];if(!$)continue;let Z=m.beginComputePass();Z.setPipeline(G),Z.setBindGroup(0,$),Z.dispatchWorkgroups(q.x,q.y??1,q.z??1),Z.end()}else{let v=_.chartPassBindGroups[Y];if(!v)continue;let K=_.perSeriesPassMeta[0]?.[Y]?.dispatch??{x:1},q=m.beginComputePass();q.setPipeline(G),q.setBindGroup(0,v),q.dispatchWorkgroups(K.x,K.y??1,K.z??1),q.end()}else if(H.type==="render"){let V=_.perSeriesPassMeta[0]?.[Y]?.draw??0,K=m.beginRenderPass({colorAttachments:[{view:_.outputTextureView,loadOp:H.loadOp??"load",storeOp:"store"}]});if(K.setPipeline(G),H.perSeries)for(let q=0;q<_.series.length;q++){let $=_.series[q];if($.pointCount===0)continue;let Z=$.passBindGroups[Y];if(!Z)continue;K.setBindGroup(0,Z),K.draw(V,1,0,q)}else{let q=_.chartPassBindGroups[Y];if(q)K.setBindGroup(0,q),K.draw(V,1,0,0)}K.end()}}let X=m.beginRenderPass({colorAttachments:[{view:N,loadOp:"clear",storeOp:"store",clearValue:{r:0,g:0,b:0,a:0}}]});if(X.setPipeline(I),_.blitBindGroup)X.setBindGroup(0,_.blitBindGroup);X.draw(4),X.end(),j.queue.submit([m.finish()])}function F(){if(!C)C=!0,requestAnimationFrame(t)}function A(_){if(_.dirty=!0,_.visible)F()}function s(){let _=!1;for(let O of J.values())if(O.dirty=!0,O.visible)_=!0;if(_)F()}function t(){C=!1;let _=performance.now();for(let O of J.values())if(O.visible&&O.dirty&&O.width>0)c(O),O.dirty=!1;M=performance.now()-_,P++}function e(){let _=0;for(let O of J.values())if(O.visible&&O.width>0)_++;return _}async function a(){if(j)return!0;if(!navigator.gpu)return postMessage({type:T.ERROR,code:Q.NO_GPU}),!1;let _=await navigator.gpu.requestAdapter();if(!_)return postMessage({type:T.ERROR,code:Q.NO_ADAPTER}),!1;j=await _.requestDevice({requiredLimits:{maxBufferSize:_.limits.maxBufferSize,maxStorageBufferBindingSize:_.limits.maxStorageBufferBindingSize}}),U=navigator.gpu.getPreferredCanvasFormat(),j.lost.then((N)=>{postMessage({type:T.ERROR,code:Q.DEVICE_LOST})}),B=j.createBindGroupLayout({entries:[{binding:0,visibility:GPUShaderStage.FRAGMENT,texture:{sampleType:"float"}},{binding:1,visibility:GPUShaderStage.FRAGMENT,sampler:{}}]});let O=j.createShaderModule({code:k});return I=j.createRenderPipeline({layout:j.createPipelineLayout({bindGroupLayouts:[B]}),vertex:{module:O,entryPoint:"vs"},fragment:{module:O,entryPoint:"fs",targets:[{format:U}]},primitive:{topology:"triangle-strip"}}),l=j.createSampler({magFilter:"linear",minFilter:"linear"}),p=setInterval(()=>{postMessage({type:T.STATS,fps:P,renderMs:M,totalCharts:J.size,activeCharts:e()}),P=0},1000),postMessage({type:T.GPU_READY}),!0}function o(_){_.dataX.destroy(),_.dataY.destroy();for(let[,O]of _.extraBuffers)O.destroy();for(let[,O]of _.seriesBuffers)O.destroy();if(_.seriesIndexBuffer)_.seriesIndexBuffer.destroy()}function r(_,O,N,m,W){let X=J.get(_);if(!X||!j)return;let Y=R.get(X.rendererName);if(!Y){postMessage({type:T.ERROR,code:Q.NO_RENDERER});return}try{X.minX=N.minX,X.maxX=N.maxX,X.minY=N.minY,X.maxY=N.maxY,X.perSeriesPassMeta=W;for(let H of X.series)o(H);if(X.series=[],X.seriesStorageBuffer)X.seriesStorageBuffer.destroy();if(O.length>0)X.seriesStorageBuffer=j.createBuffer({size:Math.max(32,O.length*32),usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST});g(X,Y,m);for(let H=0;H<O.length;H++){let G=O[H],v=j.createBuffer({size:Math.max(16,G.dataX.byteLength),usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST}),V=j.createBuffer({size:Math.max(16,G.dataY.byteLength),usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST});j.queue.writeBuffer(v,0,G.dataX),j.queue.writeBuffer(V,0,G.dataY);let K=new Map;for(let[$,Z]of Object.entries(G.extra??{})){let x=j.createBuffer({size:Math.max(16,Z.byteLength),usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST});j.queue.writeBuffer(x,0,Z),K.set($,x)}let q={label:G.label,colorR:G.colorR,colorG:G.colorG,colorB:G.colorB,dataX:v,dataY:V,extraBuffers:K,seriesBuffers:new Map,seriesIndexBuffer:null,pointCount:G.dataX.length,visibleStart:0,visibleCount:G.dataX.length,passBindGroups:[]};X.series.push(q),u(q,H,Y,m)}f(X,Y)}catch(H){postMessage({type:T.ERROR,code:Q.UPDATE})}}self.onmessage=async(_)=>{let{type:O,...N}=_.data;switch(O){case T.INIT:w=N.isDark||!1,await a();break;case T.THEME:w=N.isDark,s();break;case T.REGISTER_RENDERER:{if(!j){postMessage({type:T.ERROR,code:Q.NOT_READY});break}let m={name:N.name,shaders:N.shaders,passes:N.passes,bufferDefs:N.bufferDefs??[],uniformDefs:N.uniformDefs??[]};try{R.set(N.name,d(m))}catch(W){postMessage({type:T.ERROR,code:Q.COMPILE})}break}case T.REGISTER_CHART:{if(!j)break;let m=N.canvas.getContext("webgpu");if(!m){postMessage({type:T.ERROR,code:Q.CTX_GET});break}try{m.configure({device:j,format:U,alphaMode:"premultiplied"})}catch(G){postMessage({type:T.ERROR,code:Q.CTX_CFG});break}let W=j.limits.maxTextureDimension2D,X=Math.min(Math.max(1,Math.floor(Number(N.canvas.width)||800)),W),Y=Math.min(Math.max(1,Math.floor(Number(N.canvas.height)||400)),W),H={id:N.id,canvas:N.canvas,ctx:m,rendererName:N.rendererName,visible:!0,series:[],uniformBuffer:j.createBuffer({size:64,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),seriesStorageBuffer:null,outputTexture:null,outputTextureView:null,blitBindGroup:null,chartBuffers:new Map,customUniformBuffer:null,customUniformValues:N.customUniformValues??{},chartPassBindGroups:[],perSeriesPassMeta:N.perSeriesPassMeta??[],width:X,height:Y,panX:0,panY:0,zoomX:1,zoomY:1,minX:0,maxX:1,maxY:1,minY:0,bgColor:N.bgColor??null,dirty:!0};try{S(H)}catch(G){postMessage({type:T.ERROR,code:Q.TEX});break}J.set(N.id,H);break}case T.UNREGISTER_CHART:{let m=J.get(N.id);if(m){try{m.ctx.unconfigure()}catch{}if(m.uniformBuffer.destroy(),m.seriesStorageBuffer)m.seriesStorageBuffer.destroy();if(m.outputTexture)m.outputTexture.destroy();if(m.customUniformBuffer)m.customUniformBuffer.destroy();for(let[,W]of m.chartBuffers)W.destroy();for(let W of m.series)o(W);J.delete(N.id)}break}case T.UPDATE_SERIES:{r(N.id,N.series,N.bounds,N.bufferSizes??{},N.perSeriesPassMeta??[]);let m=J.get(N.id);if(m)A(m);break}case T.RESIZE:{let m=J.get(N.id);if(!m||N.width<=0||N.height<=0)break;let W=j.limits.maxTextureDimension2D,X=Math.min(N.width,W),Y=Math.min(N.height,W);if(X===m.width&&Y===m.height)break;if(m.width=X,m.height=Y,m.canvas.width=X,m.canvas.height=Y,N.perSeriesPassMeta?.length>0)m.perSeriesPassMeta=N.perSeriesPassMeta;let H=R.get(m.rendererName);try{if(S(m),H&&N.bufferSizes){g(m,H,N.bufferSizes);for(let G=0;G<m.series.length;G++)u(m.series[G],G,H,N.bufferSizes);f(m,H)}}catch(G){postMessage({type:T.ERROR,code:Q.RESIZE})}A(m);break}case T.VIEW_TRANSFORM:{let m=J.get(N.id);if(m)m.panX=N.panX,m.panY=N.panY,m.zoomX=Math.max(0.1,Math.min(1e6,N.zoomX)),m.zoomY=Math.max(0.1,Math.min(1e6,N.zoomY)),A(m);break}case T.BATCH_VIEW_TRANSFORM:{let m=Math.max(0.1,Math.min(1e6,N.zoomX)),W=Math.max(0.1,Math.min(1e6,N.zoomY));for(let X of N.transforms){let Y=J.get(X.id);if(Y)Y.panX=N.panX,Y.panY=N.panY,Y.zoomX=m,Y.zoomY=W,Y.dirty=!0}F();break}case T.SET_VISIBILITY:{let m=J.get(N.id);if(m){if(m.visible=N.visible,N.visible&&m.dirty)F()}break}case T.SET_STYLE:{let m=J.get(N.id);if(m){if(N.bgColor!==void 0)m.bgColor=N.bgColor;A(m)}break}case T.SET_UNIFORMS:{let m=J.get(N.id);if(!m)break;Object.assign(m.customUniformValues,N.values);let W=R.get(m.rendererName);if(W)y(m,W.config);A(m);break}}};\n';
|
|
18
|
+
|
|
19
|
+
// src/msg.ts
|
|
20
|
+
var M = {
|
|
21
|
+
INIT: 0,
|
|
22
|
+
THEME: 1,
|
|
23
|
+
REGISTER_RENDERER: 2,
|
|
24
|
+
REGISTER_CHART: 3,
|
|
25
|
+
UNREGISTER_CHART: 4,
|
|
26
|
+
UPDATE_SERIES: 5,
|
|
27
|
+
RESIZE: 6,
|
|
28
|
+
VIEW_TRANSFORM: 7,
|
|
29
|
+
BATCH_VIEW_TRANSFORM: 8,
|
|
30
|
+
SET_VISIBILITY: 9,
|
|
31
|
+
SET_STYLE: 10,
|
|
32
|
+
SET_UNIFORMS: 11,
|
|
33
|
+
GPU_READY: 12,
|
|
34
|
+
ERROR: 13,
|
|
35
|
+
STATS: 14
|
|
36
|
+
};
|
|
18
37
|
|
|
19
38
|
// src/chart-library.ts
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
39
|
+
class Chart {
|
|
40
|
+
id;
|
|
41
|
+
_mgr;
|
|
42
|
+
constructor(id, mgr) {
|
|
43
|
+
this.id = id;
|
|
44
|
+
this._mgr = mgr;
|
|
45
|
+
}
|
|
46
|
+
get _c() {
|
|
47
|
+
return this._mgr["charts"].get(this.id);
|
|
48
|
+
}
|
|
49
|
+
setData(series) {
|
|
50
|
+
this._mgr.updateSeries(this.id, series);
|
|
51
|
+
}
|
|
52
|
+
configure(patch) {
|
|
53
|
+
const c = this._c;
|
|
54
|
+
if (!c)
|
|
55
|
+
return;
|
|
56
|
+
Object.assign(c.config, patch);
|
|
57
|
+
const uniformNames = new Set((c.renderer.uniforms ?? []).map((u) => u.name));
|
|
58
|
+
const workerValues = {};
|
|
59
|
+
for (const key of Object.keys(patch)) {
|
|
60
|
+
const val = patch[key];
|
|
61
|
+
if (typeof val === "number" && uniformNames.has(key)) {
|
|
62
|
+
workerValues[key] = val;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (Object.keys(workerValues).length > 0) {
|
|
66
|
+
Object.assign(c.customUniforms, workerValues);
|
|
67
|
+
this._mgr["worker"]?.postMessage({
|
|
68
|
+
type: M.SET_UNIFORMS,
|
|
69
|
+
id: this.id,
|
|
70
|
+
values: workerValues
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if ("bgColor" in patch && patch.bgColor !== undefined) {
|
|
74
|
+
const [r, g, b] = patch.bgColor;
|
|
75
|
+
const wrap = c.el.querySelector("div");
|
|
76
|
+
if (wrap)
|
|
77
|
+
wrap.style.background = `rgb(${Math.round(r * 255)},${Math.round(g * 255)},${Math.round(b * 255)})`;
|
|
78
|
+
this._mgr["worker"]?.postMessage({
|
|
79
|
+
type: M.SET_STYLE,
|
|
80
|
+
id: this.id,
|
|
81
|
+
bgColor: patch.bgColor
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
this._mgr.requestRender(this.id);
|
|
85
|
+
this._mgr.drawChart(c);
|
|
86
|
+
}
|
|
87
|
+
addPlugin(plugin) {
|
|
88
|
+
const c = this._c;
|
|
89
|
+
if (!c || c.plugins.some((p) => p.name === plugin.name))
|
|
90
|
+
return;
|
|
91
|
+
const wrap = c.el.querySelector("div");
|
|
92
|
+
plugin.install?.(c, wrap);
|
|
93
|
+
c.plugins.push(plugin);
|
|
94
|
+
this._mgr.drawChart(c);
|
|
95
|
+
}
|
|
96
|
+
removePlugin(name) {
|
|
97
|
+
const c = this._c;
|
|
98
|
+
if (!c)
|
|
99
|
+
return;
|
|
100
|
+
const idx = c.plugins.findIndex((p) => p.name === name);
|
|
101
|
+
if (idx >= 0) {
|
|
102
|
+
c.plugins[idx].uninstall?.(c);
|
|
103
|
+
c.plugins.splice(idx, 1);
|
|
104
|
+
this._mgr.drawChart(c);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
hasPlugin(name) {
|
|
108
|
+
return this._c?.plugins.some((p) => p.name === name) ?? false;
|
|
109
|
+
}
|
|
110
|
+
resetView() {
|
|
111
|
+
this._mgr.resetView(this.id);
|
|
112
|
+
}
|
|
113
|
+
destroy() {
|
|
114
|
+
this._mgr.destroy(this.id);
|
|
24
115
|
}
|
|
25
116
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
|
|
117
|
+
var _colorEl = null;
|
|
118
|
+
function parseColor(c) {
|
|
119
|
+
if (typeof c !== "string")
|
|
120
|
+
return c;
|
|
121
|
+
if (!_colorEl) {
|
|
122
|
+
_colorEl = document.createElement("i");
|
|
123
|
+
_colorEl.style.cssText = "display:none";
|
|
124
|
+
document.body.appendChild(_colorEl);
|
|
125
|
+
}
|
|
126
|
+
_colorEl.style.color = c;
|
|
127
|
+
const m = getComputedStyle(_colorEl).color.match(/\d+/g);
|
|
128
|
+
return { r: +m[0] / 255, g: +m[1] / 255, b: +m[2] / 255 };
|
|
30
129
|
}
|
|
31
|
-
function
|
|
32
|
-
|
|
130
|
+
function resizeCanvas(canvas, cssW, cssH) {
|
|
131
|
+
const dpr = devicePixelRatio || 1;
|
|
132
|
+
canvas.width = Math.round(cssW * dpr);
|
|
133
|
+
canvas.height = Math.round(cssH * dpr);
|
|
134
|
+
if (canvas instanceof HTMLCanvasElement) {
|
|
135
|
+
canvas.style.width = `${cssW}px`;
|
|
136
|
+
canvas.style.height = `${cssH}px`;
|
|
137
|
+
}
|
|
33
138
|
}
|
|
34
139
|
|
|
35
|
-
class
|
|
140
|
+
class _ChartManager {
|
|
36
141
|
static instance = null;
|
|
37
|
-
static MARGIN = { left: 55, right: 10, top: 8, bottom: 45 };
|
|
38
|
-
static HOVER_MAX_DISTANCE_PX = 50;
|
|
39
|
-
static MIN_ZOOM = 0.1;
|
|
40
|
-
static MAX_ZOOM = 1e7;
|
|
41
|
-
static DEFAULT_LABEL_SIZE = 12;
|
|
42
|
-
static DEFAULT_FONT = '-apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif';
|
|
43
|
-
static resizeCanvas(canvas, cssWidth, cssHeight) {
|
|
44
|
-
const dpr = devicePixelRatio || 1;
|
|
45
|
-
canvas.width = Math.round(cssWidth * dpr);
|
|
46
|
-
canvas.height = Math.round(cssHeight * dpr);
|
|
47
|
-
if (canvas instanceof HTMLCanvasElement) {
|
|
48
|
-
canvas.style.width = `${cssWidth}px`;
|
|
49
|
-
canvas.style.height = `${cssHeight}px`;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
142
|
worker = null;
|
|
53
143
|
charts = new Map;
|
|
144
|
+
renderers = new Map;
|
|
145
|
+
uiPlugins = [];
|
|
146
|
+
pendingRenderers = [];
|
|
54
147
|
chartIdCounter = 0;
|
|
55
|
-
_syncViews = false;
|
|
56
148
|
_isDark = false;
|
|
149
|
+
_syncViews = false;
|
|
57
150
|
statsCallbacks = [];
|
|
58
151
|
currentStats = {
|
|
59
152
|
fps: 0,
|
|
@@ -66,55 +159,56 @@ class ChartManager {
|
|
|
66
159
|
constructor() {
|
|
67
160
|
this._isDark = document.documentElement.classList.contains("dark");
|
|
68
161
|
this.visibilityObserver = new IntersectionObserver((entries) => {
|
|
69
|
-
for (const
|
|
70
|
-
const id =
|
|
71
|
-
if (id)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
162
|
+
for (const e of entries) {
|
|
163
|
+
const id = e.target.dataset.chartId;
|
|
164
|
+
if (!id)
|
|
165
|
+
continue;
|
|
166
|
+
const chart = this.charts.get(id);
|
|
167
|
+
if (!chart)
|
|
168
|
+
continue;
|
|
169
|
+
chart.visible = e.isIntersecting;
|
|
170
|
+
this.worker?.postMessage({
|
|
171
|
+
type: M.SET_VISIBILITY,
|
|
172
|
+
id,
|
|
173
|
+
visible: e.isIntersecting
|
|
174
|
+
});
|
|
175
|
+
if (e.isIntersecting)
|
|
176
|
+
this.drawChart(chart);
|
|
85
177
|
}
|
|
86
178
|
}, { threshold: 0.01 });
|
|
87
179
|
this.resizeObserver = new ResizeObserver((entries) => {
|
|
88
|
-
for (const
|
|
89
|
-
const id =
|
|
180
|
+
for (const e of entries) {
|
|
181
|
+
const id = e.target.dataset.chartId;
|
|
90
182
|
if (!id)
|
|
91
183
|
continue;
|
|
92
184
|
const chart = this.charts.get(id);
|
|
93
185
|
if (!chart)
|
|
94
186
|
continue;
|
|
95
|
-
const { width, height } =
|
|
187
|
+
const { width, height } = e.contentRect;
|
|
96
188
|
if (width <= 0 || height <= 0)
|
|
97
189
|
continue;
|
|
98
190
|
chart.width = width;
|
|
99
191
|
chart.height = height;
|
|
100
192
|
const dpr = devicePixelRatio || 1;
|
|
193
|
+
resizeCanvas(chart.backCanvas, width, height);
|
|
194
|
+
resizeCanvas(chart.frontCanvas, width, height);
|
|
195
|
+
const { bufferSizes, perSeriesPassMeta } = this.computeRendererMeta(chart.renderer, chart);
|
|
101
196
|
this.worker?.postMessage({
|
|
102
|
-
type:
|
|
197
|
+
type: M.RESIZE,
|
|
103
198
|
id,
|
|
104
199
|
width: Math.round(width * dpr),
|
|
105
|
-
height: Math.round(height * dpr)
|
|
200
|
+
height: Math.round(height * dpr),
|
|
201
|
+
bufferSizes,
|
|
202
|
+
perSeriesPassMeta
|
|
106
203
|
});
|
|
107
|
-
ChartManager.resizeCanvas(chart.backCanvas, width, height);
|
|
108
|
-
ChartManager.resizeCanvas(chart.axisCanvas, width, height);
|
|
109
204
|
this.drawChart(chart);
|
|
110
205
|
}
|
|
111
206
|
});
|
|
112
207
|
}
|
|
113
208
|
static getInstance() {
|
|
114
|
-
if (!
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return ChartManager.instance;
|
|
209
|
+
if (!_ChartManager.instance)
|
|
210
|
+
_ChartManager.instance = new _ChartManager;
|
|
211
|
+
return _ChartManager.instance;
|
|
118
212
|
}
|
|
119
213
|
get isDark() {
|
|
120
214
|
return this._isDark;
|
|
@@ -122,6 +216,20 @@ class ChartManager {
|
|
|
122
216
|
get syncViews() {
|
|
123
217
|
return this._syncViews;
|
|
124
218
|
}
|
|
219
|
+
use(plugin) {
|
|
220
|
+
if ("passes" in plugin) {
|
|
221
|
+
const r = plugin;
|
|
222
|
+
this.renderers.set(r.name, r);
|
|
223
|
+
if (this.worker)
|
|
224
|
+
this.sendRendererRegistration(r);
|
|
225
|
+
else
|
|
226
|
+
this.pendingRenderers.push(r);
|
|
227
|
+
} else {
|
|
228
|
+
const p = plugin;
|
|
229
|
+
if (!this.uiPlugins.some((x) => x.name === p.name))
|
|
230
|
+
this.uiPlugins.push(p);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
125
233
|
async init() {
|
|
126
234
|
if (this.worker)
|
|
127
235
|
return true;
|
|
@@ -130,12 +238,12 @@ class ChartManager {
|
|
|
130
238
|
const blob = new Blob([WORKER_CODE2], {
|
|
131
239
|
type: "application/javascript"
|
|
132
240
|
});
|
|
133
|
-
|
|
134
|
-
|
|
241
|
+
this.worker = new Worker(URL.createObjectURL(blob), {
|
|
242
|
+
type: "module"
|
|
243
|
+
});
|
|
135
244
|
this.setupWorkerHandlers(resolve);
|
|
136
245
|
}).catch(() => {
|
|
137
|
-
|
|
138
|
-
this.worker = new Worker(workerUrl, { type: "module" });
|
|
246
|
+
this.worker = new Worker(new URL("./gpu-worker.js", import.meta.url), { type: "module" });
|
|
139
247
|
this.setupWorkerHandlers(resolve);
|
|
140
248
|
});
|
|
141
249
|
});
|
|
@@ -146,14 +254,17 @@ class ChartManager {
|
|
|
146
254
|
this.worker.onmessage = (e) => {
|
|
147
255
|
const { type, ...data } = e.data;
|
|
148
256
|
switch (type) {
|
|
149
|
-
case
|
|
257
|
+
case M.GPU_READY:
|
|
258
|
+
for (const r of this.pendingRenderers)
|
|
259
|
+
this.sendRendererRegistration(r);
|
|
260
|
+
this.pendingRenderers = [];
|
|
150
261
|
resolve(true);
|
|
151
262
|
break;
|
|
152
|
-
case
|
|
153
|
-
console.error("
|
|
263
|
+
case M.ERROR:
|
|
264
|
+
console.error("chartai:", data.code);
|
|
154
265
|
resolve(false);
|
|
155
266
|
break;
|
|
156
|
-
case
|
|
267
|
+
case M.STATS:
|
|
157
268
|
this.currentStats = {
|
|
158
269
|
fps: data.fps,
|
|
159
270
|
renderMs: data.renderMs,
|
|
@@ -163,227 +274,220 @@ class ChartManager {
|
|
|
163
274
|
for (const cb of this.statsCallbacks)
|
|
164
275
|
cb(this.currentStats);
|
|
165
276
|
break;
|
|
166
|
-
case "bounds-update": {
|
|
167
|
-
const chart = this.charts.get(data.id);
|
|
168
|
-
if (chart) {
|
|
169
|
-
chart.bounds = {
|
|
170
|
-
minX: data.minX,
|
|
171
|
-
maxX: data.maxX,
|
|
172
|
-
minY: data.minY,
|
|
173
|
-
maxY: data.maxY
|
|
174
|
-
};
|
|
175
|
-
this.drawChart(chart);
|
|
176
|
-
}
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
default:
|
|
180
|
-
break;
|
|
181
277
|
}
|
|
182
278
|
};
|
|
183
279
|
this.worker.onerror = (e) => {
|
|
184
|
-
console.error("
|
|
280
|
+
console.error("chartai:", e);
|
|
185
281
|
resolve(false);
|
|
186
282
|
};
|
|
187
|
-
this.worker.postMessage({ type:
|
|
283
|
+
this.worker.postMessage({ type: M.INIT, isDark: this._isDark });
|
|
188
284
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
285
|
+
sendRendererRegistration(renderer) {
|
|
286
|
+
const bufferDefs = (renderer.buffers ?? []).map((buf) => ({
|
|
287
|
+
name: buf.name,
|
|
288
|
+
usages: buf.usages,
|
|
289
|
+
perSeries: renderer.passes.some((p) => p.perSeries !== false && p.bindings.some((b) => b.source === buf.name))
|
|
290
|
+
}));
|
|
291
|
+
this.worker?.postMessage({
|
|
292
|
+
type: M.REGISTER_RENDERER,
|
|
293
|
+
name: renderer.name,
|
|
294
|
+
shaders: renderer.shaders,
|
|
295
|
+
passes: renderer.passes.map((p) => ({
|
|
296
|
+
type: p.type,
|
|
297
|
+
shader: p.shader,
|
|
298
|
+
bindings: p.bindings,
|
|
299
|
+
perSeries: p.perSeries !== false,
|
|
300
|
+
topology: p.topology,
|
|
301
|
+
loadOp: p.loadOp,
|
|
302
|
+
blend: p.blend
|
|
303
|
+
})),
|
|
304
|
+
bufferDefs,
|
|
305
|
+
uniformDefs: renderer.uniforms ?? []
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
computeRendererMeta(renderer, chart) {
|
|
309
|
+
const bufferSizes = {};
|
|
310
|
+
const perSeriesPassMeta = [];
|
|
311
|
+
const series = chart.series.length > 0 ? chart.series : [
|
|
312
|
+
{
|
|
313
|
+
rawX: [],
|
|
314
|
+
rawY: [],
|
|
315
|
+
extra: {},
|
|
316
|
+
label: "",
|
|
317
|
+
color: { r: 0, g: 0, b: 0 }
|
|
318
|
+
}
|
|
319
|
+
];
|
|
320
|
+
const dpr = devicePixelRatio || 1;
|
|
321
|
+
const physW = Math.round(chart.width * dpr);
|
|
322
|
+
const physH = Math.round(chart.height * dpr);
|
|
323
|
+
for (const s of series) {
|
|
324
|
+
const ctx = {
|
|
325
|
+
width: physW,
|
|
326
|
+
height: physH,
|
|
327
|
+
samples: s.rawX.length,
|
|
328
|
+
seriesCount: series.length,
|
|
329
|
+
bounds: chart.bounds,
|
|
330
|
+
view: chart.view
|
|
331
|
+
};
|
|
332
|
+
for (const buf of renderer.buffers ?? []) {
|
|
333
|
+
const size = buf.bytes(ctx);
|
|
334
|
+
bufferSizes[buf.name] = Math.max(bufferSizes[buf.name] ?? 0, size);
|
|
335
|
+
}
|
|
336
|
+
perSeriesPassMeta.push(renderer.passes.map((p) => ({
|
|
337
|
+
dispatch: p.dispatch?.(ctx),
|
|
338
|
+
draw: p.draw?.(ctx)
|
|
339
|
+
})));
|
|
192
340
|
}
|
|
341
|
+
return { bufferSizes, perSeriesPassMeta };
|
|
342
|
+
}
|
|
343
|
+
create(config) {
|
|
344
|
+
if (!this.worker)
|
|
345
|
+
throw new Error("Call init() before create().");
|
|
346
|
+
const renderer = this.renderers.get(config.type);
|
|
347
|
+
if (!renderer)
|
|
348
|
+
throw new Error(`No renderer "${config.type}". Call manager.use() with a matching RendererPlugin first.`);
|
|
193
349
|
const id = `chart-${++this.chartIdCounter}`;
|
|
194
350
|
const el = document.createElement("div");
|
|
195
351
|
el.dataset.chartId = id;
|
|
196
|
-
el.style.cssText = "width:
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
el.appendChild(
|
|
352
|
+
el.style.cssText = "width:100%;height:100%;position:relative;";
|
|
353
|
+
const wrap = document.createElement("div");
|
|
354
|
+
wrap.dataset.chartId = id;
|
|
355
|
+
wrap.style.cssText = "width:100%;height:100%;position:relative;";
|
|
356
|
+
const mkCanvas = (z, events) => {
|
|
357
|
+
const c = document.createElement("canvas");
|
|
358
|
+
c.style.cssText = `position:absolute;inset:0;width:100%;height:100%;pointer-events:${events};z-index:${z};`;
|
|
359
|
+
return c;
|
|
360
|
+
};
|
|
361
|
+
const backCanvas = mkCanvas(0, "none");
|
|
362
|
+
const gpuCanvas = mkCanvas(1, "auto");
|
|
363
|
+
const frontCanvas = mkCanvas(2, "none");
|
|
364
|
+
wrap.append(backCanvas, gpuCanvas, frontCanvas);
|
|
365
|
+
el.appendChild(wrap);
|
|
210
366
|
config.container.appendChild(el);
|
|
211
367
|
let offscreen;
|
|
212
368
|
try {
|
|
213
369
|
offscreen = gpuCanvas.transferControlToOffscreen();
|
|
214
370
|
} catch (e) {
|
|
215
|
-
|
|
216
|
-
throw new Error(`Failed to create chart ${id}: ${e}`);
|
|
371
|
+
throw new Error(`Failed to acquire OffscreenCanvas: ${e}`);
|
|
217
372
|
}
|
|
218
|
-
const rect =
|
|
373
|
+
const rect = wrap.getBoundingClientRect();
|
|
219
374
|
const cssW = rect.width || 400;
|
|
220
375
|
const cssH = rect.height || 200;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
376
|
+
resizeCanvas(offscreen, cssW, cssH);
|
|
377
|
+
resizeCanvas(backCanvas, cssW, cssH);
|
|
378
|
+
resizeCanvas(frontCanvas, cssW, cssH);
|
|
224
379
|
if (config.bgColor) {
|
|
225
380
|
const [r, g, b] = config.bgColor;
|
|
226
|
-
|
|
381
|
+
wrap.style.background = `rgb(${Math.round(r * 255)},${Math.round(g * 255)},${Math.round(b * 255)})`;
|
|
382
|
+
}
|
|
383
|
+
const customUniforms = {};
|
|
384
|
+
for (const u of renderer.uniforms ?? []) {
|
|
385
|
+
const v = config[u.name];
|
|
386
|
+
customUniforms[u.name] = typeof v === "number" ? v : u.default;
|
|
227
387
|
}
|
|
228
388
|
const chart = {
|
|
229
389
|
id,
|
|
230
390
|
config,
|
|
231
391
|
el,
|
|
232
392
|
backCanvas,
|
|
233
|
-
|
|
393
|
+
frontCanvas,
|
|
234
394
|
width: cssW,
|
|
235
395
|
height: cssH,
|
|
236
396
|
series: [],
|
|
237
397
|
bounds: { minX: 0, maxX: 1, minY: 0, maxY: 1 },
|
|
238
398
|
view: { panX: 0, panY: 0, zoomX: 1, zoomY: 1 },
|
|
239
|
-
zoomMode: config.zoomMode || "both",
|
|
240
399
|
visible: true,
|
|
241
400
|
dragging: false,
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
fontFamily: config.fontFamily,
|
|
246
|
-
momentum: null,
|
|
247
|
-
findNearestPoint(screenX, screenY, width, height) {
|
|
248
|
-
if (this.series.length === 0)
|
|
249
|
-
return null;
|
|
250
|
-
const normX = screenX / width;
|
|
251
|
-
const normY = screenY / height;
|
|
252
|
-
const rangeX = this.bounds.maxX - this.bounds.minX;
|
|
253
|
-
const rangeY = this.bounds.maxY - this.bounds.minY;
|
|
254
|
-
const viewWidth = rangeX / this.view.zoomX;
|
|
255
|
-
const viewHeight = rangeY / this.view.zoomY;
|
|
256
|
-
const viewMinX = this.bounds.minX + this.view.panX * rangeX;
|
|
257
|
-
const viewMinY = this.bounds.minY + this.view.panY * rangeY;
|
|
258
|
-
const dataX = viewMinX + normX * viewWidth;
|
|
259
|
-
const dataY = viewMinY + (1 - normY) * viewHeight;
|
|
260
|
-
let bestSeriesIdx = -1;
|
|
261
|
-
let bestIdx = -1;
|
|
262
|
-
let bestDistX = Infinity;
|
|
263
|
-
let bestDistY = Infinity;
|
|
264
|
-
for (let s = 0;s < this.series.length; s++) {
|
|
265
|
-
const series2 = this.series[s];
|
|
266
|
-
const n = series2.rawX.length;
|
|
267
|
-
if (n === 0)
|
|
268
|
-
continue;
|
|
269
|
-
let lo = 0, hi = n - 1;
|
|
270
|
-
while (lo < hi) {
|
|
271
|
-
const mid = lo + hi >> 1;
|
|
272
|
-
if (series2.rawX[mid] < dataX)
|
|
273
|
-
lo = mid + 1;
|
|
274
|
-
else
|
|
275
|
-
hi = mid;
|
|
276
|
-
}
|
|
277
|
-
let idx = lo;
|
|
278
|
-
if (lo > 0) {
|
|
279
|
-
const distLo = Math.abs(series2.rawX[lo] - dataX);
|
|
280
|
-
const distPrev = Math.abs(series2.rawX[lo - 1] - dataX);
|
|
281
|
-
if (distPrev < distLo)
|
|
282
|
-
idx = lo - 1;
|
|
283
|
-
}
|
|
284
|
-
const distX = Math.abs(series2.rawX[idx] - dataX);
|
|
285
|
-
const distY = Math.abs(series2.rawY[idx] - dataY);
|
|
286
|
-
if (distX < bestDistX || distX === bestDistX && distY < bestDistY) {
|
|
287
|
-
bestDistX = distX;
|
|
288
|
-
bestDistY = distY;
|
|
289
|
-
bestSeriesIdx = s;
|
|
290
|
-
bestIdx = idx;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
if (bestSeriesIdx === -1)
|
|
294
|
-
return null;
|
|
295
|
-
const series = this.series[bestSeriesIdx];
|
|
296
|
-
const pointNormX = (series.rawX[bestIdx] - viewMinX) / viewWidth;
|
|
297
|
-
const pointScreenX = pointNormX * width;
|
|
298
|
-
const pixelDistX = Math.abs(pointScreenX - screenX);
|
|
299
|
-
if (pixelDistX > ChartManager.HOVER_MAX_DISTANCE_PX)
|
|
300
|
-
return null;
|
|
301
|
-
return {
|
|
302
|
-
x: series.rawX[bestIdx],
|
|
303
|
-
y: series.rawY[bestIdx],
|
|
304
|
-
index: bestIdx,
|
|
305
|
-
screenX,
|
|
306
|
-
screenY,
|
|
307
|
-
seriesIndex: bestSeriesIdx,
|
|
308
|
-
seriesLabel: series.label
|
|
309
|
-
};
|
|
310
|
-
}
|
|
401
|
+
plugins: [...this.uiPlugins],
|
|
402
|
+
renderer,
|
|
403
|
+
customUniforms
|
|
311
404
|
};
|
|
312
405
|
this.charts.set(id, chart);
|
|
313
|
-
const
|
|
406
|
+
const dpr = devicePixelRatio || 1;
|
|
407
|
+
const { bufferSizes, perSeriesPassMeta } = this.computeRendererMeta(renderer, chart);
|
|
314
408
|
this.worker.postMessage({
|
|
315
|
-
type:
|
|
409
|
+
type: M.REGISTER_CHART,
|
|
316
410
|
id,
|
|
317
411
|
canvas: offscreen,
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
412
|
+
rendererName: config.type,
|
|
413
|
+
bgColor: config.bgColor ?? null,
|
|
414
|
+
bufferSizes,
|
|
415
|
+
perSeriesPassMeta,
|
|
416
|
+
customUniformValues: customUniforms,
|
|
417
|
+
width: Math.round(cssW * dpr),
|
|
418
|
+
height: Math.round(cssH * dpr)
|
|
322
419
|
}, [offscreen]);
|
|
323
420
|
this.visibilityObserver.observe(el);
|
|
324
|
-
this.resizeObserver.observe(
|
|
325
|
-
for (const plugin of
|
|
326
|
-
plugin.install?.(chart,
|
|
327
|
-
|
|
421
|
+
this.resizeObserver.observe(wrap);
|
|
422
|
+
for (const plugin of chart.plugins)
|
|
423
|
+
plugin.install?.(chart, wrap);
|
|
424
|
+
renderer.install?.(chart, wrap);
|
|
328
425
|
this.updateSeries(id, config.series);
|
|
329
|
-
return id;
|
|
426
|
+
return new Chart(id, this);
|
|
330
427
|
}
|
|
331
428
|
destroy(id) {
|
|
332
429
|
const chart = this.charts.get(id);
|
|
333
430
|
if (!chart)
|
|
334
431
|
return;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (chart.momentum)
|
|
339
|
-
cancelAnimationFrame(chart.momentum);
|
|
432
|
+
chart.renderer.uninstall?.(chart);
|
|
433
|
+
for (const p of chart.plugins)
|
|
434
|
+
p.uninstall?.(chart);
|
|
340
435
|
this.visibilityObserver.unobserve(chart.el);
|
|
341
|
-
const wrap = chart.el.querySelector("
|
|
436
|
+
const wrap = chart.el.querySelector("div");
|
|
342
437
|
if (wrap)
|
|
343
438
|
this.resizeObserver.unobserve(wrap);
|
|
344
439
|
chart.el.remove();
|
|
345
|
-
this.worker?.postMessage({ type:
|
|
440
|
+
this.worker?.postMessage({ type: M.UNREGISTER_CHART, id });
|
|
346
441
|
this.charts.delete(id);
|
|
347
442
|
}
|
|
348
443
|
updateSeries(id, series) {
|
|
349
444
|
const chart = this.charts.get(id);
|
|
350
|
-
if (!chart || !this.worker)
|
|
351
|
-
return;
|
|
352
|
-
if (series.length === 0)
|
|
445
|
+
if (!chart || !this.worker || series.length === 0)
|
|
353
446
|
return;
|
|
354
447
|
chart.series = series.map((s) => {
|
|
355
448
|
const n = s.x.length;
|
|
449
|
+
const color = parseColor(s.color);
|
|
356
450
|
if (n === 0)
|
|
357
|
-
return { label: s.label, color
|
|
358
|
-
const
|
|
359
|
-
|
|
451
|
+
return { label: s.label, color, rawX: [], rawY: [], extra: {} };
|
|
452
|
+
const idx = Array.from({ length: n }, (_, i) => i).sort((a, b) => s.x[a] - s.x[b]);
|
|
453
|
+
const extra = {};
|
|
454
|
+
for (const key in s) {
|
|
455
|
+
if (key !== "label" && key !== "color" && key !== "x" && key !== "y" && Array.isArray(s[key])) {
|
|
456
|
+
extra[key] = idx.map((i) => s[key][i]);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
360
459
|
return {
|
|
361
460
|
label: s.label,
|
|
362
|
-
color
|
|
363
|
-
rawX:
|
|
364
|
-
rawY:
|
|
461
|
+
color,
|
|
462
|
+
rawX: idx.map((i) => s.x[i]),
|
|
463
|
+
rawY: idx.map((i) => s.y[i]),
|
|
464
|
+
extra
|
|
365
465
|
};
|
|
366
466
|
});
|
|
367
|
-
|
|
368
|
-
let
|
|
369
|
-
|
|
370
|
-
for (
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
467
|
+
const customBounds = chart.renderer.computeBounds?.(chart.series);
|
|
468
|
+
let { minX, maxX, minY, maxY } = customBounds ?? (() => {
|
|
469
|
+
let minX2 = Infinity, maxX2 = -Infinity, minY2 = Infinity, maxY2 = -Infinity;
|
|
470
|
+
for (const s of chart.series) {
|
|
471
|
+
for (let i = 0;i < s.rawX.length; i++) {
|
|
472
|
+
if (s.rawX[i] < minX2)
|
|
473
|
+
minX2 = s.rawX[i];
|
|
474
|
+
if (s.rawX[i] > maxX2)
|
|
475
|
+
maxX2 = s.rawX[i];
|
|
476
|
+
if (s.rawY[i] < minY2)
|
|
477
|
+
minY2 = s.rawY[i];
|
|
478
|
+
if (s.rawY[i] > maxY2)
|
|
479
|
+
maxY2 = s.rawY[i];
|
|
480
|
+
}
|
|
379
481
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
482
|
+
const px = (maxX2 - minX2) * 0.05 || 1;
|
|
483
|
+
const py = (maxY2 - minY2) * 0.1 || 1;
|
|
484
|
+
return {
|
|
485
|
+
minX: minX2 - px,
|
|
486
|
+
maxX: maxX2 + px,
|
|
487
|
+
minY: minY2 - py,
|
|
488
|
+
maxY: maxY2 + py
|
|
489
|
+
};
|
|
490
|
+
})();
|
|
387
491
|
const db = chart.config.defaultBounds;
|
|
388
492
|
if (db) {
|
|
389
493
|
if (db.minX !== undefined)
|
|
@@ -396,89 +500,45 @@ class ChartManager {
|
|
|
396
500
|
maxY = db.maxY;
|
|
397
501
|
}
|
|
398
502
|
chart.bounds = { minX, maxX, minY, maxY };
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
503
|
+
const { bufferSizes, perSeriesPassMeta } = this.computeRendererMeta(chart.renderer, chart);
|
|
504
|
+
const seriesData = chart.series.map((s) => {
|
|
505
|
+
const extra = {};
|
|
506
|
+
for (const key in s.extra)
|
|
507
|
+
extra[key] = new Float32Array(s.extra[key]);
|
|
508
|
+
return {
|
|
509
|
+
label: s.label,
|
|
510
|
+
colorR: s.color.r,
|
|
511
|
+
colorG: s.color.g,
|
|
512
|
+
colorB: s.color.b,
|
|
513
|
+
dataX: new Float32Array(s.rawX),
|
|
514
|
+
dataY: new Float32Array(s.rawY),
|
|
515
|
+
extra
|
|
516
|
+
};
|
|
517
|
+
});
|
|
518
|
+
const transferables = seriesData.flatMap((s) => [
|
|
519
|
+
s.dataX.buffer,
|
|
520
|
+
s.dataY.buffer,
|
|
521
|
+
...Object.values(s.extra).map((a) => a.buffer)
|
|
522
|
+
]);
|
|
407
523
|
this.worker.postMessage({
|
|
408
|
-
type:
|
|
524
|
+
type: M.UPDATE_SERIES,
|
|
409
525
|
id,
|
|
410
526
|
series: seriesData,
|
|
411
|
-
bounds: chart.bounds
|
|
412
|
-
|
|
527
|
+
bounds: chart.bounds,
|
|
528
|
+
bufferSizes,
|
|
529
|
+
perSeriesPassMeta
|
|
530
|
+
}, transferables);
|
|
413
531
|
this.sendViewTransform(chart);
|
|
414
532
|
this.drawChart(chart);
|
|
415
533
|
}
|
|
416
|
-
setPointSize(id, size) {
|
|
417
|
-
const chart = this.charts.get(id);
|
|
418
|
-
if (!chart)
|
|
419
|
-
return;
|
|
420
|
-
this.worker?.postMessage({
|
|
421
|
-
type: "set-point-size",
|
|
422
|
-
id,
|
|
423
|
-
pointSize: Math.max(1, Math.min(8, Math.round(size)))
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
setMaxSamplesPerPixel(id, maxSamples) {
|
|
427
|
-
const chart = this.charts.get(id);
|
|
428
|
-
if (!chart)
|
|
429
|
-
return;
|
|
430
|
-
this.worker?.postMessage({
|
|
431
|
-
type: "set-max-samples",
|
|
432
|
-
id,
|
|
433
|
-
maxSamplesPerPixel: Math.max(0, maxSamples | 0)
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
setStyle(id, style) {
|
|
437
|
-
const chart = this.charts.get(id);
|
|
438
|
-
if (!chart)
|
|
439
|
-
return;
|
|
440
|
-
if (style.bgColor !== undefined)
|
|
441
|
-
chart.bgColor = style.bgColor;
|
|
442
|
-
if (style.textColor !== undefined)
|
|
443
|
-
chart.textColor = style.textColor;
|
|
444
|
-
if (style.gridColor !== undefined)
|
|
445
|
-
chart.gridColor = style.gridColor;
|
|
446
|
-
if (style.fontFamily !== undefined)
|
|
447
|
-
chart.fontFamily = style.fontFamily;
|
|
448
|
-
if (style.bgColor !== undefined) {
|
|
449
|
-
const wrap = chart.el.querySelector(".gpu-chart-canvas-wrap");
|
|
450
|
-
if (wrap) {
|
|
451
|
-
const [r, g, b] = style.bgColor;
|
|
452
|
-
wrap.style.background = `rgb(${Math.round(r * 255)},${Math.round(g * 255)},${Math.round(b * 255)})`;
|
|
453
|
-
}
|
|
454
|
-
this.worker?.postMessage({
|
|
455
|
-
type: "set-style",
|
|
456
|
-
id,
|
|
457
|
-
bgColor: style.bgColor
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
this.drawChart(chart);
|
|
461
|
-
}
|
|
462
|
-
setZoomMode(id, mode) {
|
|
463
|
-
const chart = this.charts.get(id);
|
|
464
|
-
if (chart)
|
|
465
|
-
chart.zoomMode = mode;
|
|
466
|
-
}
|
|
467
|
-
getChartCount() {
|
|
468
|
-
return this.charts.size;
|
|
469
|
-
}
|
|
470
|
-
getZoomMode(id) {
|
|
471
|
-
return this.charts.get(id)?.zoomMode || "both";
|
|
472
|
-
}
|
|
473
534
|
setSyncViews(sync) {
|
|
474
535
|
this._syncViews = sync;
|
|
475
536
|
}
|
|
476
537
|
setTheme(dark) {
|
|
477
538
|
this._isDark = dark;
|
|
478
|
-
this.worker?.postMessage({ type:
|
|
479
|
-
for (const chart of this.charts.values())
|
|
539
|
+
this.worker?.postMessage({ type: M.THEME, isDark: dark });
|
|
540
|
+
for (const chart of this.charts.values())
|
|
480
541
|
this.drawChart(chart);
|
|
481
|
-
}
|
|
482
542
|
}
|
|
483
543
|
onStats(callback) {
|
|
484
544
|
this.statsCallbacks.push(callback);
|
|
@@ -495,22 +555,17 @@ class ChartManager {
|
|
|
495
555
|
const chart = this.charts.get(id);
|
|
496
556
|
if (!chart)
|
|
497
557
|
return;
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const startPanX = chart.view.panX;
|
|
503
|
-
const startPanY = chart.view.panY;
|
|
504
|
-
const startZoomX = chart.view.zoomX;
|
|
505
|
-
const startZoomY = chart.view.zoomY;
|
|
506
|
-
const startTime = performance.now();
|
|
558
|
+
for (const p of chart.plugins)
|
|
559
|
+
p.resetView?.(chart);
|
|
560
|
+
const { panX: spx, panY: spy, zoomX: szx, zoomY: szy } = chart.view;
|
|
561
|
+
const t0 = performance.now();
|
|
507
562
|
const animate = () => {
|
|
508
|
-
const t = Math.min(1, (performance.now() -
|
|
509
|
-
const
|
|
510
|
-
chart.view.panX =
|
|
511
|
-
chart.view.panY =
|
|
512
|
-
chart.view.zoomX =
|
|
513
|
-
chart.view.zoomY =
|
|
563
|
+
const t = Math.min(1, (performance.now() - t0) / 300);
|
|
564
|
+
const e = 1 - Math.pow(1 - t, 3);
|
|
565
|
+
chart.view.panX = spx * (1 - e);
|
|
566
|
+
chart.view.panY = spy * (1 - e);
|
|
567
|
+
chart.view.zoomX = szx + (1 - szx) * e;
|
|
568
|
+
chart.view.zoomY = szy + (1 - szy) * e;
|
|
514
569
|
this.sendViewTransform(chart);
|
|
515
570
|
this.drawChart(chart);
|
|
516
571
|
if (this._syncViews)
|
|
@@ -520,9 +575,14 @@ class ChartManager {
|
|
|
520
575
|
};
|
|
521
576
|
requestAnimationFrame(animate);
|
|
522
577
|
}
|
|
578
|
+
requestRender(id) {
|
|
579
|
+
const chart = this.charts.get(id);
|
|
580
|
+
if (chart)
|
|
581
|
+
this.sendViewTransform(chart);
|
|
582
|
+
}
|
|
523
583
|
sendViewTransform(chart) {
|
|
524
584
|
this.worker?.postMessage({
|
|
525
|
-
type:
|
|
585
|
+
type: M.VIEW_TRANSFORM,
|
|
526
586
|
id: chart.id,
|
|
527
587
|
panX: chart.view.panX,
|
|
528
588
|
panY: chart.view.panY,
|
|
@@ -541,7 +601,7 @@ class ChartManager {
|
|
|
541
601
|
}
|
|
542
602
|
if (transforms.length > 0) {
|
|
543
603
|
this.worker?.postMessage({
|
|
544
|
-
type:
|
|
604
|
+
type: M.BATCH_VIEW_TRANSFORM,
|
|
545
605
|
panX: source.view.panX,
|
|
546
606
|
panY: source.view.panY,
|
|
547
607
|
zoomX: source.view.zoomX,
|
|
@@ -559,26 +619,23 @@ class ChartManager {
|
|
|
559
619
|
backCtx.clearRect(0, 0, chart.backCanvas.width, chart.backCanvas.height);
|
|
560
620
|
backCtx.save();
|
|
561
621
|
backCtx.scale(dpr, dpr);
|
|
562
|
-
for (const
|
|
563
|
-
|
|
564
|
-
}
|
|
622
|
+
for (const p of chart.plugins)
|
|
623
|
+
p.beforeDraw?.(backCtx, chart);
|
|
565
624
|
backCtx.restore();
|
|
566
625
|
}
|
|
567
|
-
const ctx = chart.
|
|
626
|
+
const ctx = chart.frontCanvas.getContext("2d");
|
|
568
627
|
if (ctx) {
|
|
569
|
-
ctx.clearRect(0, 0, chart.
|
|
628
|
+
ctx.clearRect(0, 0, chart.frontCanvas.width, chart.frontCanvas.height);
|
|
570
629
|
ctx.save();
|
|
571
630
|
ctx.scale(dpr, dpr);
|
|
572
|
-
for (const
|
|
573
|
-
|
|
574
|
-
}
|
|
631
|
+
for (const p of chart.plugins)
|
|
632
|
+
p.afterDraw?.(ctx, chart);
|
|
575
633
|
ctx.restore();
|
|
576
634
|
}
|
|
577
635
|
}
|
|
578
636
|
}
|
|
637
|
+
var ChartManager = _ChartManager.getInstance();
|
|
579
638
|
export {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
getPlugins,
|
|
583
|
-
ChartManager
|
|
639
|
+
ChartManager,
|
|
640
|
+
Chart
|
|
584
641
|
};
|