@jbrowse/render-core 4.3.0 → 5.0.0-beta.10
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/esm/RenderCanvas.d.ts +2 -2
- package/esm/RenderLifecycleMixin.d.ts +52 -20
- package/esm/RenderLifecycleMixin.js +89 -23
- package/esm/canvas2dUtils.d.ts +90 -23
- package/esm/canvas2dUtils.js +119 -36
- package/esm/colorRampLut.d.ts +5 -5
- package/esm/colorRampLut.js +6 -6
- package/esm/computePipeline.d.ts +14 -0
- package/esm/computePipeline.js +62 -0
- package/esm/contractReports.d.ts +54 -0
- package/esm/contractReports.js +112 -0
- package/esm/coverageBand.d.ts +13 -8
- package/esm/coverageBand.js +15 -5
- package/esm/coverageBandBuffers.d.ts +2 -9
- package/esm/coverageBandBuffers.js +1 -13
- package/esm/createRenderingBackend.d.ts +14 -18
- package/esm/createRenderingBackend.js +4 -2
- package/esm/displayPhase.d.ts +83 -104
- package/esm/displayPhase.js +42 -82
- package/esm/encodeMemo.d.ts +14 -0
- package/esm/encodeMemo.js +48 -0
- package/esm/gpuContextLostError.d.ts +30 -0
- package/esm/gpuContextLostError.js +40 -0
- package/esm/gpuDevice.d.ts +29 -10
- package/esm/gpuDevice.js +35 -16
- package/esm/graphicsCapabilities.d.ts +27 -19
- package/esm/graphicsCapabilities.js +40 -18
- package/esm/hal/createHal.d.ts +4 -5
- package/esm/hal/createHal.js +14 -4
- package/esm/hal/deviceGpuCache.d.ts +25 -30
- package/esm/hal/deviceGpuCache.js +88 -97
- package/esm/hal/gpuHalBase.d.ts +5 -4
- package/esm/hal/gpuHalBase.js +25 -14
- package/esm/hal/index.d.ts +2 -2
- package/esm/hal/index.js +1 -1
- package/esm/hal/mockHal.d.ts +15 -10
- package/esm/hal/mockHal.js +31 -18
- package/esm/hal/oomReporter.d.ts +7 -0
- package/esm/hal/oomReporter.js +10 -0
- package/esm/hal/passIds.d.ts +8 -7
- package/esm/hal/passIds.js +11 -10
- package/esm/hal/regionRegistry.d.ts +0 -1
- package/esm/hal/regionRegistry.js +3 -15
- package/esm/hal/types.d.ts +55 -18
- package/esm/hal/webgl2Hal.d.ts +23 -6
- package/esm/hal/webgl2Hal.js +161 -155
- package/esm/hal/webgpuHal.d.ts +29 -13
- package/esm/hal/webgpuHal.js +221 -153
- package/esm/installUpload.d.ts +16 -3
- package/esm/installUpload.js +13 -41
- package/esm/instanceCache.d.ts +10 -7
- package/esm/instanceCache.js +14 -15
- package/esm/instancePass.d.ts +2 -1
- package/esm/mapUploadSync.d.ts +2 -10
- package/esm/mapUploadSync.js +2 -3
- package/esm/marks/barMark.d.ts +30 -0
- package/esm/marks/barMark.js +149 -0
- package/esm/marks/colorFill.d.ts +17 -0
- package/esm/marks/colorFill.js +26 -0
- package/esm/marks/drawAgainstHit.d.ts +113 -0
- package/esm/marks/drawAgainstHit.js +407 -0
- package/esm/marks/ellipseDistance.d.ts +10 -0
- package/esm/marks/ellipseDistance.js +136 -0
- package/esm/marks/glyphPaint.d.ts +36 -0
- package/esm/marks/glyphPaint.js +77 -0
- package/esm/marks/index.d.ts +33 -0
- package/esm/marks/index.js +20 -0
- package/esm/marks/lineMark.d.ts +37 -0
- package/esm/marks/lineMark.js +416 -0
- package/esm/marks/linkMark.d.ts +76 -0
- package/esm/marks/linkMark.js +585 -0
- package/esm/marks/markBackend.d.ts +118 -0
- package/esm/marks/markBackend.js +196 -0
- package/esm/marks/markHit.d.ts +32 -0
- package/esm/marks/markHit.js +61 -0
- package/esm/marks/markInk.d.ts +18 -0
- package/esm/marks/markInk.js +71 -0
- package/esm/marks/markPaint.d.ts +14 -0
- package/esm/marks/markPaint.js +32 -0
- package/esm/marks/markPlan.d.ts +25 -0
- package/esm/marks/markPlan.js +20 -0
- package/esm/marks/markRamp.d.ts +72 -0
- package/esm/marks/markRamp.js +197 -0
- package/esm/marks/nearestMarkHit.d.ts +55 -0
- package/esm/marks/nearestMarkHit.js +158 -0
- package/esm/marks/pathRecorder.d.ts +14 -0
- package/esm/marks/pathRecorder.js +80 -0
- package/esm/marks/pointMark.d.ts +42 -0
- package/esm/marks/pointMark.js +125 -0
- package/esm/marks/rowLane.d.ts +39 -0
- package/esm/marks/rowLane.js +41 -0
- package/esm/marks/rowSpanIndex.d.ts +29 -0
- package/esm/marks/rowSpanIndex.js +141 -0
- package/esm/marks/rowTable.d.ts +41 -0
- package/esm/marks/rowTable.js +75 -0
- package/esm/marks/ruleMark.d.ts +21 -0
- package/esm/marks/ruleMark.js +91 -0
- package/esm/marks/spanMark.d.ts +42 -0
- package/esm/marks/spanMark.js +81 -0
- package/esm/marks/types.d.ts +261 -0
- package/esm/marks/types.js +175 -0
- package/esm/marks/valueScale.d.ts +23 -0
- package/esm/marks/valueScale.js +11 -0
- package/esm/namedReactions.d.ts +3 -4
- package/esm/namedReactions.js +3 -4
- package/esm/perRegionRenderingBackend.d.ts +21 -3
- package/esm/perRegionRenderingBackend.js +37 -3
- package/esm/regionDataMap.d.ts +2 -2
- package/esm/regionDataMap.js +25 -23
- package/esm/renderBlock.d.ts +14 -0
- package/esm/renderBlock.js +25 -0
- package/esm/renderingBackendBase.d.ts +20 -14
- package/esm/renderingBackendBase.js +12 -7
- package/esm/scoreScale.d.ts +27 -0
- package/esm/scoreScale.js +88 -0
- package/esm/shaders/barMark.generated.d.ts +3 -0
- package/esm/shaders/barMark.generated.js +7 -0
- package/esm/shaders/barMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/barMark.glsl.generated.js +4 -0
- package/esm/shaders/barMark.iface.generated.d.ts +90 -0
- package/esm/shaders/barMark.iface.generated.js +205 -0
- package/esm/shaders/barMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/barMark.wgsl.generated.js +3 -0
- package/esm/shaders/capsule.js.generated.d.ts +1 -0
- package/esm/shaders/capsule.js.generated.js +11 -0
- package/esm/shaders/clipStrip.generated.d.ts +2 -0
- package/esm/shaders/clipStrip.generated.js +4 -0
- package/esm/shaders/clipStrip.js.generated.d.ts +1 -0
- package/esm/shaders/clipStrip.js.generated.js +21 -0
- package/esm/shaders/colorRampLut.js.generated.d.ts +1 -0
- package/esm/shaders/colorRampLut.js.generated.js +17 -0
- package/esm/shaders/coverageBar.generated.d.ts +2 -3
- package/esm/shaders/coverageBar.generated.js +4 -3
- package/esm/shaders/coverageBar.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageBar.glsl.generated.js +4 -0
- package/esm/shaders/coverageBar.iface.generated.js +1 -1
- package/esm/shaders/coverageBar.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageBar.wgsl.generated.js +3 -0
- package/esm/shaders/coverageClip.generated.d.ts +3 -0
- package/esm/shaders/coverageClip.generated.js +7 -0
- package/esm/shaders/coverageClip.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageClip.glsl.generated.js +4 -0
- package/esm/shaders/coverageClip.iface.generated.d.ts +85 -0
- package/esm/shaders/coverageClip.iface.generated.js +114 -0
- package/esm/shaders/coverageClip.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageClip.wgsl.generated.js +3 -0
- package/esm/shaders/coverageIndicator.generated.d.ts +2 -3
- package/esm/shaders/coverageIndicator.generated.js +4 -3
- package/esm/shaders/coverageIndicator.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageIndicator.glsl.generated.js +4 -0
- package/esm/shaders/coverageIndicator.iface.generated.js +1 -1
- package/esm/shaders/coverageIndicator.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageIndicator.wgsl.generated.js +3 -0
- package/esm/shaders/coverageInterbase.generated.d.ts +2 -3
- package/esm/shaders/coverageInterbase.generated.js +4 -3
- package/esm/shaders/coverageInterbase.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageInterbase.glsl.generated.js +4 -0
- package/esm/shaders/coverageInterbase.iface.generated.js +1 -1
- package/esm/shaders/coverageInterbase.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageInterbase.wgsl.generated.js +3 -0
- package/esm/shaders/coverageMod.generated.d.ts +2 -3
- package/esm/shaders/coverageMod.generated.js +4 -3
- package/esm/shaders/coverageMod.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageMod.glsl.generated.js +4 -0
- package/esm/shaders/coverageMod.iface.generated.js +1 -1
- package/esm/shaders/coverageMod.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageMod.wgsl.generated.js +3 -0
- package/esm/shaders/coverageSnp.generated.d.ts +2 -3
- package/esm/shaders/coverageSnp.generated.js +4 -3
- package/esm/shaders/coverageSnp.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageSnp.glsl.generated.js +4 -0
- package/esm/shaders/coverageSnp.iface.generated.js +1 -1
- package/esm/shaders/coverageSnp.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageSnp.wgsl.generated.js +3 -0
- package/esm/shaders/curveDistance.js.generated.d.ts +5 -0
- package/esm/shaders/curveDistance.js.generated.js +87 -0
- package/esm/shaders/hpmath.js.generated.d.ts +2 -1
- package/esm/shaders/hpmath.js.generated.js +5 -2
- package/esm/shaders/lineCenterMark.generated.d.ts +3 -0
- package/esm/shaders/lineCenterMark.generated.js +7 -0
- package/esm/shaders/lineCenterMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/lineCenterMark.glsl.generated.js +4 -0
- package/esm/shaders/lineCenterMark.iface.generated.d.ts +104 -0
- package/esm/shaders/lineCenterMark.iface.generated.js +237 -0
- package/esm/shaders/lineCenterMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/lineCenterMark.wgsl.generated.js +3 -0
- package/esm/shaders/lineCommon.generated.d.ts +2 -0
- package/esm/shaders/lineCommon.generated.js +4 -0
- package/esm/shaders/lineStepMark.generated.d.ts +3 -0
- package/esm/shaders/lineStepMark.generated.js +7 -0
- package/esm/shaders/lineStepMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/lineStepMark.glsl.generated.js +4 -0
- package/esm/shaders/lineStepMark.iface.generated.d.ts +98 -0
- package/esm/shaders/lineStepMark.iface.generated.js +225 -0
- package/esm/shaders/lineStepMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/lineStepMark.wgsl.generated.js +3 -0
- package/esm/shaders/linkMark.consts.generated.d.ts +14 -0
- package/esm/shaders/linkMark.consts.generated.js +16 -0
- package/esm/shaders/linkMark.generated.d.ts +4 -0
- package/esm/shaders/linkMark.generated.js +8 -0
- package/esm/shaders/linkMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/linkMark.glsl.generated.js +4 -0
- package/esm/shaders/linkMark.iface.generated.d.ts +142 -0
- package/esm/shaders/linkMark.iface.generated.js +1829 -0
- package/esm/shaders/linkMark.js.generated.d.ts +7 -0
- package/esm/shaders/linkMark.js.generated.js +159 -0
- package/esm/shaders/linkMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/linkMark.wgsl.generated.js +3 -0
- package/esm/shaders/markColor.generated.d.ts +9 -0
- package/esm/shaders/markColor.generated.js +11 -0
- package/esm/shaders/pointGlyph.js.generated.d.ts +2 -0
- package/esm/shaders/pointGlyph.js.generated.js +15 -0
- package/esm/shaders/pointMark.consts.generated.d.ts +7 -0
- package/esm/shaders/pointMark.consts.generated.js +9 -0
- package/esm/shaders/pointMark.generated.d.ts +4 -0
- package/esm/shaders/pointMark.generated.js +8 -0
- package/esm/shaders/pointMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/pointMark.glsl.generated.js +4 -0
- package/esm/shaders/pointMark.iface.generated.d.ts +98 -0
- package/esm/shaders/pointMark.iface.generated.js +219 -0
- package/esm/shaders/pointMark.js.generated.d.ts +3 -0
- package/esm/shaders/pointMark.js.generated.js +93 -0
- package/esm/shaders/pointMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/pointMark.wgsl.generated.js +3 -0
- package/esm/shaders/rowRect.generated.d.ts +0 -1
- package/esm/shaders/rowRect.generated.js +0 -1
- package/esm/shaders/rowTable.generated.d.ts +1 -0
- package/esm/shaders/rowTable.generated.js +3 -0
- package/esm/shaders/rowTable.js.generated.d.ts +5 -0
- package/esm/shaders/rowTable.js.generated.js +29 -0
- package/esm/shaders/scoreScale.generated.d.ts +2 -0
- package/esm/shaders/scoreScale.generated.js +4 -0
- package/esm/shaders/scoreScale.js.generated.d.ts +1 -0
- package/esm/shaders/scoreScale.js.generated.js +50 -23
- package/esm/shaders/spanMark.generated.d.ts +3 -0
- package/esm/shaders/spanMark.generated.js +7 -0
- package/esm/shaders/spanMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/spanMark.glsl.generated.js +4 -0
- package/esm/shaders/spanMark.iface.generated.d.ts +81 -0
- package/esm/shaders/spanMark.iface.generated.js +215 -0
- package/esm/shaders/spanMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/spanMark.wgsl.generated.js +3 -0
- package/esm/sharedBackendKey.d.ts +18 -0
- package/esm/{keyedRenderingBackend.js → sharedBackendKey.js} +5 -0
- package/esm/slangPass.d.ts +18 -11
- package/esm/slangPass.js +12 -4
- package/esm/useGraphicsCapabilities.d.ts +1 -1
- package/esm/useGraphicsCapabilities.js +1 -1
- package/esm/useRenderingBackend.d.ts +13 -13
- package/esm/useRenderingBackend.js +58 -20
- package/esm/webgpuUtils.d.ts +3 -11
- package/esm/webgpuUtils.js +12 -33
- package/package.json +32 -7
- package/src/shaders/antialias.slang +46 -0
- package/src/shaders/arcHull.test.ts +267 -0
- package/src/shaders/barMark.generated.ts +11 -0
- package/src/shaders/barMark.glsl.generated.ts +6 -0
- package/src/shaders/barMark.iface.generated.ts +285 -0
- package/src/shaders/barMark.slang +167 -0
- package/src/shaders/barMark.wgsl.generated.ts +4 -0
- package/src/shaders/capsule.js.generated.ts +14 -0
- package/src/shaders/capsule.slang +12 -4
- package/src/shaders/clipStrip.generated.ts +6 -0
- package/src/shaders/clipStrip.js.generated.ts +22 -0
- package/src/shaders/clipStrip.slang +34 -0
- package/src/shaders/clipStrip.test.ts +39 -0
- package/src/shaders/colorPack.slang +5 -1
- package/src/shaders/colorRampLut.js.generated.ts +22 -0
- package/src/shaders/colorRampLut.slang +15 -1
- package/src/shaders/coverageBand.slang +64 -32
- package/src/shaders/coverageBar.generated.ts +6 -5
- package/src/shaders/coverageBar.glsl.generated.ts +6 -0
- package/src/shaders/coverageBar.iface.generated.ts +1 -1
- package/src/shaders/coverageBar.slang +10 -10
- package/src/shaders/coverageBar.wgsl.generated.ts +4 -0
- package/src/shaders/coverageClip.generated.ts +11 -0
- package/src/shaders/coverageClip.glsl.generated.ts +6 -0
- package/src/shaders/coverageClip.iface.generated.ts +172 -0
- package/src/shaders/coverageClip.slang +38 -0
- package/src/shaders/coverageClip.wgsl.generated.ts +4 -0
- package/src/shaders/coverageIndicator.generated.ts +6 -5
- package/src/shaders/coverageIndicator.glsl.generated.ts +6 -0
- package/src/shaders/coverageIndicator.iface.generated.ts +1 -1
- package/src/shaders/coverageIndicator.wgsl.generated.ts +4 -0
- package/src/shaders/coverageInterbase.generated.ts +6 -5
- package/src/shaders/coverageInterbase.glsl.generated.ts +6 -0
- package/src/shaders/coverageInterbase.iface.generated.ts +1 -1
- package/src/shaders/coverageInterbase.wgsl.generated.ts +4 -0
- package/src/shaders/coverageMod.generated.ts +6 -5
- package/src/shaders/coverageMod.glsl.generated.ts +6 -0
- package/src/shaders/coverageMod.iface.generated.ts +1 -1
- package/src/shaders/coverageMod.wgsl.generated.ts +4 -0
- package/src/shaders/coverageSnp.generated.ts +6 -5
- package/src/shaders/coverageSnp.glsl.generated.ts +6 -0
- package/src/shaders/coverageSnp.iface.generated.ts +1 -1
- package/src/shaders/coverageSnp.wgsl.generated.ts +4 -0
- package/src/shaders/curveDistance.js.generated.ts +91 -0
- package/src/shaders/curveDistance.slang +265 -0
- package/src/shaders/hpClampFloor.test.ts +87 -0
- package/src/shaders/hpmath.js.generated.ts +6 -2
- package/src/shaders/hpmath.slang +100 -48
- package/src/shaders/insetValueScale.test.ts +67 -0
- package/src/shaders/lineCenterMark.generated.ts +11 -0
- package/src/shaders/lineCenterMark.glsl.generated.ts +6 -0
- package/src/shaders/lineCenterMark.iface.generated.ts +328 -0
- package/src/shaders/lineCenterMark.slang +127 -0
- package/src/shaders/lineCenterMark.wgsl.generated.ts +4 -0
- package/src/shaders/lineCommon.generated.ts +6 -0
- package/src/shaders/lineCommon.slang +102 -0
- package/src/shaders/lineStepMark.generated.ts +11 -0
- package/src/shaders/lineStepMark.glsl.generated.ts +6 -0
- package/src/shaders/lineStepMark.iface.generated.ts +311 -0
- package/src/shaders/lineStepMark.slang +125 -0
- package/src/shaders/lineStepMark.wgsl.generated.ts +4 -0
- package/src/shaders/linkMark.consts.generated.ts +30 -0
- package/src/shaders/linkMark.generated.ts +12 -0
- package/src/shaders/linkMark.glsl.generated.ts +6 -0
- package/src/shaders/linkMark.iface.generated.ts +1954 -0
- package/src/shaders/linkMark.js.generated.ts +167 -0
- package/src/shaders/linkMark.slang +439 -0
- package/src/shaders/linkMark.wgsl.generated.ts +4 -0
- package/src/shaders/markColor.generated.ts +20 -0
- package/src/shaders/markColor.slang +128 -0
- package/src/shaders/pointGlyph.js.generated.ts +20 -0
- package/src/shaders/pointGlyph.slang +23 -7
- package/src/shaders/pointGlyphPad.test.ts +242 -0
- package/src/shaders/pointMark.consts.generated.ts +16 -0
- package/src/shaders/pointMark.generated.ts +12 -0
- package/src/shaders/pointMark.glsl.generated.ts +6 -0
- package/src/shaders/pointMark.iface.generated.ts +304 -0
- package/src/shaders/pointMark.js.generated.ts +99 -0
- package/src/shaders/pointMark.slang +227 -0
- package/src/shaders/pointMark.wgsl.generated.ts +4 -0
- package/src/shaders/rowRect.generated.ts +0 -2
- package/src/shaders/rowRect.slang +39 -58
- package/src/shaders/rowTable.generated.ts +4 -0
- package/src/shaders/rowTable.js.generated.ts +37 -0
- package/src/shaders/rowTable.slang +98 -0
- package/src/shaders/scoreScale.generated.ts +6 -0
- package/src/shaders/scoreScale.js.generated.ts +48 -23
- package/src/shaders/scoreScale.slang +49 -55
- package/src/shaders/sdEllipse.test.ts +211 -0
- package/src/shaders/spanMark.generated.ts +11 -0
- package/src/shaders/spanMark.glsl.generated.ts +6 -0
- package/src/shaders/spanMark.iface.generated.ts +289 -0
- package/src/shaders/spanMark.slang +77 -0
- package/src/shaders/spanMark.wgsl.generated.ts +4 -0
- package/src/shaders/valueScale.slang +26 -0
- package/src/shaders/wideCircleLeg.test.ts +85 -0
- package/esm/globalRenderingBackend.d.ts +0 -90
- package/esm/globalRenderingBackend.js +0 -53
- package/esm/keyedRenderingBackend.d.ts +0 -41
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { SCALE_TYPE_LOG, rampMidNorm, scaleTypeCode } from '../scoreScale.js';
|
|
2
|
+
import { rampMidT } from '../shaders/colorRampLut.js.generated.js';
|
|
3
|
+
import { MAX_COLOR_BANDS, MAX_COLOR_CUTS, RAMP_LINEAR, RAMP_LOG, RAMP_NONE, RAMP_NOT_A_NUMBER_COLOR, RAMP_NO_VALUE_BITS, RAMP_NO_VALUE_COLOR, RAMP_THRESHOLD, } from '../shaders/markColor.generated.js';
|
|
4
|
+
import { normalizeScore } from '../shaders/scoreScale.js.generated.js';
|
|
5
|
+
const NO_COLORS = new Uint32Array(0);
|
|
6
|
+
/** Whether a colour scale is a threshold rather than a ramp. */
|
|
7
|
+
export function isThreshold(scale) {
|
|
8
|
+
return 'cuts' in scale;
|
|
9
|
+
}
|
|
10
|
+
function midNormOf({ domain, scale, mid }) {
|
|
11
|
+
return rampMidNorm(domain[0], domain[1], scaleTypeCode(scale), mid);
|
|
12
|
+
}
|
|
13
|
+
const NO_RGBA = [0, 0, 0, 0];
|
|
14
|
+
function bandsOf(colorAt) {
|
|
15
|
+
return Array.from({ length: MAX_COLOR_BANDS }, (_, i) => colorAt(i));
|
|
16
|
+
}
|
|
17
|
+
function rgbaOf(abgr) {
|
|
18
|
+
return [
|
|
19
|
+
(abgr & 255) / 255,
|
|
20
|
+
((abgr >>> 8) & 255) / 255,
|
|
21
|
+
((abgr >>> 16) & 255) / 255,
|
|
22
|
+
((abgr >>> 24) & 255) / 255,
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
function quad(values, from) {
|
|
26
|
+
return [
|
|
27
|
+
values[from] ?? 0,
|
|
28
|
+
values[from + 1] ?? 0,
|
|
29
|
+
values[from + 2] ?? 0,
|
|
30
|
+
values[from + 3] ?? 0,
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* `markColor.slang`'s uniforms for a frame's colour scale, or for none: the
|
|
35
|
+
* mode, a ramp's domain and middle, and a threshold's cuts and band colours.
|
|
36
|
+
* Every valued shape's uniform block carries the set.
|
|
37
|
+
*/
|
|
38
|
+
export function rampUniforms(scale) {
|
|
39
|
+
const none = {
|
|
40
|
+
rampMode: RAMP_NONE,
|
|
41
|
+
rampMin: 0,
|
|
42
|
+
rampMax: 1,
|
|
43
|
+
rampMidNorm: 0.5,
|
|
44
|
+
colorCutCount: 0,
|
|
45
|
+
colorCuts: [quad([], 0), quad([], 0)],
|
|
46
|
+
colorBands: bandsOf(() => NO_RGBA),
|
|
47
|
+
};
|
|
48
|
+
if (!scale) {
|
|
49
|
+
return none;
|
|
50
|
+
}
|
|
51
|
+
if (isThreshold(scale)) {
|
|
52
|
+
const cuts = scale.cuts.slice(0, MAX_COLOR_CUTS);
|
|
53
|
+
return {
|
|
54
|
+
...none,
|
|
55
|
+
rampMode: RAMP_THRESHOLD,
|
|
56
|
+
colorCutCount: cuts.length,
|
|
57
|
+
colorCuts: [quad(cuts, 0), quad(cuts, 4)],
|
|
58
|
+
colorBands: bandsOf(i => i <= cuts.length ? rgbaOf(scale.colors[i] ?? 0) : NO_RGBA),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
...none,
|
|
63
|
+
rampMode: scale.scale === 'log' ? RAMP_LOG : RAMP_LINEAR,
|
|
64
|
+
rampMin: scale.domain[0],
|
|
65
|
+
rampMax: scale.domain[1],
|
|
66
|
+
rampMidNorm: midNormOf(scale),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The 4-byte instance lane, for the packer. Under a scale it is the value's
|
|
71
|
+
* float32 bits viewed as the `uint` the attribute declares — the reinterpret
|
|
72
|
+
* `markColor.slang`'s `asfloat` undoes, and the reason a quantitative colour
|
|
73
|
+
* adds no lane. A constant colour fills a lane for the pack alone, which the
|
|
74
|
+
* payload never holds.
|
|
75
|
+
*/
|
|
76
|
+
export function colorBits(c, count) {
|
|
77
|
+
const { colorValue, color } = c;
|
|
78
|
+
return colorValue
|
|
79
|
+
? rampValueBits(colorValue)
|
|
80
|
+
: typeof color === 'number'
|
|
81
|
+
? new Uint32Array(count).fill(color)
|
|
82
|
+
: (color ?? NO_COLORS);
|
|
83
|
+
}
|
|
84
|
+
/** Instance `i`'s `color` alone, as a one-instance channel carries it. */
|
|
85
|
+
export function instanceColor(color, i) {
|
|
86
|
+
return typeof color === 'number' ? color : color?.subarray(i, i + 1);
|
|
87
|
+
}
|
|
88
|
+
/** A value lane's float32 values viewed as their bits. */
|
|
89
|
+
export function rampValueBits(values) {
|
|
90
|
+
return new Uint32Array(values.buffer, values.byteOffset, values.length);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* A value lane's values kept where `keep` says, copied as bits: a JS number
|
|
94
|
+
* read out of the lane may lose the payload `RAMP_NO_VALUE_BITS` marks a
|
|
95
|
+
* feature with no value by.
|
|
96
|
+
*/
|
|
97
|
+
export function keepRampValues(values, keep) {
|
|
98
|
+
const kept = rampValueBits(values).filter((_, i) => keep(values[i], i));
|
|
99
|
+
return new Float32Array(kept.buffer, kept.byteOffset, kept.length);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The interval a value is in on a threshold: how many of the ascending cuts it
|
|
103
|
+
* is at or past, `thresholdIndex`'s rule and the shader's `thresholdBand`.
|
|
104
|
+
*/
|
|
105
|
+
export function thresholdBandOf(value, cuts) {
|
|
106
|
+
let band = 0;
|
|
107
|
+
for (const cut of cuts) {
|
|
108
|
+
if (value >= cut) {
|
|
109
|
+
band++;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return band;
|
|
113
|
+
}
|
|
114
|
+
function sameScale(a, b) {
|
|
115
|
+
if (isThreshold(a) || isThreshold(b)) {
|
|
116
|
+
return (isThreshold(a) &&
|
|
117
|
+
isThreshold(b) &&
|
|
118
|
+
a.cuts.length === b.cuts.length &&
|
|
119
|
+
a.cuts.every((cut, i) => cut === b.cuts[i]) &&
|
|
120
|
+
a.colors.length === b.colors.length &&
|
|
121
|
+
a.colors.every((color, i) => color === b.colors[i]));
|
|
122
|
+
}
|
|
123
|
+
return (a.domain[0] === b.domain[0] &&
|
|
124
|
+
a.domain[1] === b.domain[1] &&
|
|
125
|
+
a.scale === b.scale &&
|
|
126
|
+
a.lut === b.lut &&
|
|
127
|
+
midNormOf(a) === midNormOf(b));
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The packed colours a painter fills with. Under a scale the values are baked
|
|
131
|
+
* against it once and kept on the payload, so a repaint at an unchanged scale
|
|
132
|
+
* — every pan and every hover — walks no values and the painters' fill
|
|
133
|
+
* batching still sees runs of one colour. The bake reruns when the domain, the
|
|
134
|
+
* scale type, the ramp's bytes or a threshold's cuts or colours move. A
|
|
135
|
+
* constant colour expands once and is kept the same way.
|
|
136
|
+
*/
|
|
137
|
+
export function paintColors(c, count, scale) {
|
|
138
|
+
const { colorValue, color } = c;
|
|
139
|
+
if (!scale || !colorValue) {
|
|
140
|
+
return typeof color === 'number'
|
|
141
|
+
? constantColors(c, color, count)
|
|
142
|
+
: (color ?? NO_COLORS);
|
|
143
|
+
}
|
|
144
|
+
const bake = c.rampBake;
|
|
145
|
+
if (bake?.values === colorValue &&
|
|
146
|
+
bake.colors.length === count &&
|
|
147
|
+
sameScale(bake.scale, scale)) {
|
|
148
|
+
return bake.colors;
|
|
149
|
+
}
|
|
150
|
+
const colors = new Uint32Array(count);
|
|
151
|
+
const bits = rampValueBits(colorValue);
|
|
152
|
+
const colorOf = isThreshold(scale)
|
|
153
|
+
? (value) => Number.isFinite(value)
|
|
154
|
+
? scale.colors[thresholdBandOf(value, scale.cuts)]
|
|
155
|
+
: scale.colors[value > 0 ? scale.cuts.length : 0]
|
|
156
|
+
: rampColorOf(scale);
|
|
157
|
+
for (let i = 0; i < count; i++) {
|
|
158
|
+
const value = colorValue[i];
|
|
159
|
+
colors[i] =
|
|
160
|
+
bits[i] === RAMP_NO_VALUE_BITS
|
|
161
|
+
? RAMP_NO_VALUE_COLOR
|
|
162
|
+
: Number.isNaN(value)
|
|
163
|
+
? RAMP_NOT_A_NUMBER_COLOR
|
|
164
|
+
: colorOf(value);
|
|
165
|
+
}
|
|
166
|
+
c.rampBake = { values: colorValue, scale, colors };
|
|
167
|
+
return colors;
|
|
168
|
+
}
|
|
169
|
+
function constantColors(c, color, count) {
|
|
170
|
+
const bake = c.constantBake;
|
|
171
|
+
if (bake?.length === count && (count === 0 || bake[0] === color)) {
|
|
172
|
+
return bake;
|
|
173
|
+
}
|
|
174
|
+
const colors = new Uint32Array(count).fill(color);
|
|
175
|
+
c.constantBake = colors;
|
|
176
|
+
return colors;
|
|
177
|
+
}
|
|
178
|
+
function rampColorOf(ramp) {
|
|
179
|
+
const [min, max] = ramp.domain;
|
|
180
|
+
const log = ramp.scale === 'log';
|
|
181
|
+
const midNorm = midNormOf(ramp);
|
|
182
|
+
const { lut } = ramp;
|
|
183
|
+
const entries = lut.length / 4;
|
|
184
|
+
return (value) => {
|
|
185
|
+
const t = rampMidT(Number.isFinite(value)
|
|
186
|
+
? normalizeScore(value, min, max, log ? SCALE_TYPE_LOG : 0, 1)
|
|
187
|
+
: value > 0
|
|
188
|
+
? 1
|
|
189
|
+
: 0, midNorm);
|
|
190
|
+
const o = Math.round(t * (entries - 1)) * 4;
|
|
191
|
+
return (((lut[o + 3] << 24) |
|
|
192
|
+
(lut[o + 2] << 16) |
|
|
193
|
+
(lut[o + 1] << 8) |
|
|
194
|
+
lut[o]) >>>
|
|
195
|
+
0);
|
|
196
|
+
};
|
|
197
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { RenderBlock } from '../renderBlock.ts';
|
|
2
|
+
import type { RowParams } from './rowLane.ts';
|
|
3
|
+
import type { Mark, MarkFrame, MarkHit } from './types.ts';
|
|
4
|
+
import type { MarkValueScale } from './valueScale.ts';
|
|
5
|
+
/**
|
|
6
|
+
* What a cursor's grab radius spans in one block for one mark: the bp, and
|
|
7
|
+
* the values the mark's shape can put ink at, every value for a shape with no
|
|
8
|
+
* `valueWindow`.
|
|
9
|
+
*/
|
|
10
|
+
export interface HitWindow {
|
|
11
|
+
block: RenderBlock;
|
|
12
|
+
bpMin: number;
|
|
13
|
+
bpMax: number;
|
|
14
|
+
valueMin: number;
|
|
15
|
+
valueMax: number;
|
|
16
|
+
}
|
|
17
|
+
/** A {@link MarkHit}, with the mark, block and region it was found in. */
|
|
18
|
+
export interface NearestMarkHit<TRegion> extends MarkHit {
|
|
19
|
+
mark: number;
|
|
20
|
+
block: RenderBlock;
|
|
21
|
+
region: TRegion;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The instance nearest `(xPx, yPx)` within `radiusPx`, over the blocks the
|
|
25
|
+
* radius reaches. Marks are asked last to first and only a strictly nearer hit
|
|
26
|
+
* replaces the best, so a tie goes to the mark on top. `candidates` names what
|
|
27
|
+
* a mark is asked about in a block: what an index finds in the reach, or
|
|
28
|
+
* `backToFront` over every instance. `undefined` leaves the mark out, as a
|
|
29
|
+
* closed gate does. A mark spanning the view is asked first, over every region
|
|
30
|
+
* in `regionKeys` (the blocks' own by default) with an unbounded bp reach,
|
|
31
|
+
* since it paints over every block.
|
|
32
|
+
*/
|
|
33
|
+
export declare function nearestMarkHit<TRegion, TState extends MarkFrame>(marks: readonly Mark<TRegion, TState>[], blocks: readonly RenderBlock[], regionOf: (displayedRegionIndex: number) => TRegion | undefined, state: TState, xPx: number, yPx: number, { radiusPx, candidates, regionKeys, }: {
|
|
34
|
+
radiusPx: number;
|
|
35
|
+
regionKeys?: Iterable<number>;
|
|
36
|
+
candidates: (region: TRegion, mark: number, reach: HitWindow) => Iterable<number> | undefined;
|
|
37
|
+
}): NearestMarkHit<TRegion> | undefined;
|
|
38
|
+
/** `[end - 1 .. start]`: every instance as candidates, back to front. */
|
|
39
|
+
export declare function backToFront(start: number, end: number): Iterable<number>;
|
|
40
|
+
/**
|
|
41
|
+
* A value-scaled shape's `valueWindow`, read back through `denormalizeScore`
|
|
42
|
+
* inside each `rowHeight` band the radius touches — the whole canvas for a
|
|
43
|
+
* shape with no rows — the bands starting `rowOffsetPx` down, as the shapes
|
|
44
|
+
* place them. An end within reach of a band edge opens to infinity, where an
|
|
45
|
+
* out-of-domain value clamps, so a cursor near the seam reaches the
|
|
46
|
+
* neighbouring band too, and so does one within `edgePx` of it, where a bar's
|
|
47
|
+
* clip strip stands. A point passes its `insetPx`; a bar passes its `origin`,
|
|
48
|
+
* and its window opens away from the origin on the cursor's side, or both
|
|
49
|
+
* ways where the radius reaches the origin, which every bar stands on.
|
|
50
|
+
*/
|
|
51
|
+
export declare function valueWindow(yPx: number, radiusPx: number, { canvasHeight }: MarkFrame, scale: RowParams & MarkValueScale & {
|
|
52
|
+
insetPx?: number;
|
|
53
|
+
edgePx?: number;
|
|
54
|
+
origin?: number;
|
|
55
|
+
}): [number, number];
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { bpAtPxExact } from '../canvas2dUtils.js';
|
|
2
|
+
import { canvasWideBlock } from '../renderBlock.js';
|
|
3
|
+
import { denormalizeScore } from '../scoreScale.js';
|
|
4
|
+
import { valueScaleUniforms } from './valueScale.js';
|
|
5
|
+
const EVERY_VALUE = [-Infinity, Infinity];
|
|
6
|
+
/**
|
|
7
|
+
* The instance nearest `(xPx, yPx)` within `radiusPx`, over the blocks the
|
|
8
|
+
* radius reaches. Marks are asked last to first and only a strictly nearer hit
|
|
9
|
+
* replaces the best, so a tie goes to the mark on top. `candidates` names what
|
|
10
|
+
* a mark is asked about in a block: what an index finds in the reach, or
|
|
11
|
+
* `backToFront` over every instance. `undefined` leaves the mark out, as a
|
|
12
|
+
* closed gate does. A mark spanning the view is asked first, over every region
|
|
13
|
+
* in `regionKeys` (the blocks' own by default) with an unbounded bp reach,
|
|
14
|
+
* since it paints over every block.
|
|
15
|
+
*/
|
|
16
|
+
export function nearestMarkHit(marks, blocks, regionOf, state, xPx, yPx, { radiusPx, candidates, regionKeys, }) {
|
|
17
|
+
let best;
|
|
18
|
+
let bestDistSq = radiusPx * radiusPx;
|
|
19
|
+
const ask = (m, block, region, reach) => {
|
|
20
|
+
const mark = marks[m];
|
|
21
|
+
if (!mark.hitNearest) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const values = mark.valueWindow
|
|
25
|
+
? mark.valueWindow(region, block, state, yPx, radiusPx)
|
|
26
|
+
: EVERY_VALUE;
|
|
27
|
+
const asked = values &&
|
|
28
|
+
candidates(region, m, {
|
|
29
|
+
block,
|
|
30
|
+
...reach,
|
|
31
|
+
valueMin: values[0],
|
|
32
|
+
valueMax: values[1],
|
|
33
|
+
});
|
|
34
|
+
const hit = asked &&
|
|
35
|
+
mark.hitNearest(region, block, state, xPx, yPx, asked, bestDistSq);
|
|
36
|
+
if (hit) {
|
|
37
|
+
bestDistSq = hit.distSq;
|
|
38
|
+
best = {
|
|
39
|
+
mark: m,
|
|
40
|
+
block,
|
|
41
|
+
region,
|
|
42
|
+
index: hit.index,
|
|
43
|
+
x: hit.x,
|
|
44
|
+
y: hit.y,
|
|
45
|
+
distSq: hit.distSq,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
if (marks.some(m => m.spansView)) {
|
|
50
|
+
for (const key of regionKeys ?? blocks.map(b => b.displayedRegionIndex)) {
|
|
51
|
+
const region = regionOf(key);
|
|
52
|
+
if (region === undefined) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const block = canvasWideBlock(key, state.canvasWidth);
|
|
56
|
+
for (let m = marks.length - 1; m >= 0; m--) {
|
|
57
|
+
if (marks[m].spansView) {
|
|
58
|
+
ask(m, block, region, { bpMin: -Infinity, bpMax: Infinity });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const block of blocks) {
|
|
64
|
+
const region = regionOf(block.displayedRegionIndex);
|
|
65
|
+
const widthPx = block.screenEndPx - block.screenStartPx;
|
|
66
|
+
if (region === undefined || widthPx <= 0) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const halfBp = (radiusPx * (block.end - block.start)) / widthPx;
|
|
70
|
+
const cursorBp = bpAtPxExact(xPx, block);
|
|
71
|
+
const bpMin = cursorBp - halfBp;
|
|
72
|
+
const bpMax = cursorBp + halfBp;
|
|
73
|
+
if (bpMax < block.start || bpMin > block.end) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
for (let m = marks.length - 1; m >= 0; m--) {
|
|
77
|
+
if (!marks[m].spansView) {
|
|
78
|
+
ask(m, block, region, { bpMin, bpMax });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return best;
|
|
83
|
+
}
|
|
84
|
+
/** `[end - 1 .. start]`: every instance as candidates, back to front. */
|
|
85
|
+
export function backToFront(start, end) {
|
|
86
|
+
return new ReverseRange(start, end);
|
|
87
|
+
}
|
|
88
|
+
// A class rather than a generator: `for..of` steps this in a few ns where a
|
|
89
|
+
// generator yields in tens.
|
|
90
|
+
class ReverseRange {
|
|
91
|
+
i;
|
|
92
|
+
start;
|
|
93
|
+
result = { value: 0, done: false };
|
|
94
|
+
constructor(start, end) {
|
|
95
|
+
this.start = start;
|
|
96
|
+
this.i = end;
|
|
97
|
+
}
|
|
98
|
+
[Symbol.iterator]() {
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
next() {
|
|
102
|
+
const r = this.result;
|
|
103
|
+
if (this.i > this.start) {
|
|
104
|
+
this.i--;
|
|
105
|
+
r.value = this.i;
|
|
106
|
+
r.done = false;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
r.done = true;
|
|
110
|
+
}
|
|
111
|
+
return r;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* A value-scaled shape's `valueWindow`, read back through `denormalizeScore`
|
|
116
|
+
* inside each `rowHeight` band the radius touches — the whole canvas for a
|
|
117
|
+
* shape with no rows — the bands starting `rowOffsetPx` down, as the shapes
|
|
118
|
+
* place them. An end within reach of a band edge opens to infinity, where an
|
|
119
|
+
* out-of-domain value clamps, so a cursor near the seam reaches the
|
|
120
|
+
* neighbouring band too, and so does one within `edgePx` of it, where a bar's
|
|
121
|
+
* clip strip stands. A point passes its `insetPx`; a bar passes its `origin`,
|
|
122
|
+
* and its window opens away from the origin on the cursor's side, or both
|
|
123
|
+
* ways where the radius reaches the origin, which every bar stands on.
|
|
124
|
+
*/
|
|
125
|
+
export function valueWindow(yPx, radiusPx, { canvasHeight }, scale) {
|
|
126
|
+
const { domain, insetPx = 0, edgePx = 0, origin } = scale;
|
|
127
|
+
const { valueScaleType, valueSymlogConstant } = valueScaleUniforms(scale);
|
|
128
|
+
const height = scale.rowHeight ?? canvasHeight;
|
|
129
|
+
const offset = scale.rowOffsetPx ?? 0;
|
|
130
|
+
const inset = Math.min(insetPx, height / 2);
|
|
131
|
+
const bands = height > 0 ? Math.ceil((canvasHeight - offset) / height) : 1;
|
|
132
|
+
const bandAt = (y) => bands > 1
|
|
133
|
+
? Math.max(0, Math.min(bands - 1, Math.floor((y - offset) / height)))
|
|
134
|
+
: 0;
|
|
135
|
+
const lastBand = bandAt(yPx + radiusPx);
|
|
136
|
+
let valueMin = Infinity;
|
|
137
|
+
let valueMax = -Infinity;
|
|
138
|
+
for (let b = bandAt(yPx - radiusPx); b <= lastBand; b++) {
|
|
139
|
+
const top = offset + b * height;
|
|
140
|
+
const valueAt = (y) => denormalizeScore(1 - (y - top - inset) / (height - 2 * inset), domain[0], domain[1], valueScaleType, valueSymlogConstant);
|
|
141
|
+
const lo = yPx + radiusPx >= top + height - inset - edgePx
|
|
142
|
+
? -Infinity
|
|
143
|
+
: valueAt(yPx + radiusPx);
|
|
144
|
+
const hi = yPx - radiusPx <= top + inset + edgePx
|
|
145
|
+
? Infinity
|
|
146
|
+
: valueAt(yPx - radiusPx);
|
|
147
|
+
const [bandMin, bandMax] = origin === undefined
|
|
148
|
+
? [lo, hi]
|
|
149
|
+
: lo > origin
|
|
150
|
+
? [lo, Infinity]
|
|
151
|
+
: hi < origin
|
|
152
|
+
? [-Infinity, hi]
|
|
153
|
+
: [-Infinity, Infinity];
|
|
154
|
+
valueMin = Math.min(valueMin, bandMin);
|
|
155
|
+
valueMax = Math.max(valueMax, bandMax);
|
|
156
|
+
}
|
|
157
|
+
return [valueMin, valueMax];
|
|
158
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MarkContext2D } from './types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* A 2D context that records what a painter traces as one SVG path, with the
|
|
4
|
+
* widest stroke and the dash it stroked with, so a highlight lies on the ink a
|
|
5
|
+
* shape painted rather than on a second tracing of it. Transforms are not
|
|
6
|
+
* followed; a painter that sets one records untransformed.
|
|
7
|
+
*/
|
|
8
|
+
export declare function recordPath(): {
|
|
9
|
+
ctx: MarkContext2D;
|
|
10
|
+
readonly d: string;
|
|
11
|
+
/** The widest stroke traced, 0 for a shape that only fills. */
|
|
12
|
+
readonly lineWidth: number;
|
|
13
|
+
readonly dash: string | undefined;
|
|
14
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const ELLIPSE_SEGMENTS = 48;
|
|
2
|
+
/**
|
|
3
|
+
* A 2D context that records what a painter traces as one SVG path, with the
|
|
4
|
+
* widest stroke and the dash it stroked with, so a highlight lies on the ink a
|
|
5
|
+
* shape painted rather than on a second tracing of it. Transforms are not
|
|
6
|
+
* followed; a painter that sets one records untransformed.
|
|
7
|
+
*/
|
|
8
|
+
export function recordPath() {
|
|
9
|
+
const parts = [];
|
|
10
|
+
let dash = [];
|
|
11
|
+
let strokedDash = [];
|
|
12
|
+
let strokedWidth = 0;
|
|
13
|
+
const ctx = {
|
|
14
|
+
fillStyle: '',
|
|
15
|
+
strokeStyle: '',
|
|
16
|
+
lineWidth: 1,
|
|
17
|
+
lineCap: 'butt',
|
|
18
|
+
lineJoin: 'miter',
|
|
19
|
+
save() { },
|
|
20
|
+
restore() { },
|
|
21
|
+
clip() { },
|
|
22
|
+
beginPath() { },
|
|
23
|
+
translate() { },
|
|
24
|
+
scale() { },
|
|
25
|
+
rotate() { },
|
|
26
|
+
rect(x, y, w, h) {
|
|
27
|
+
parts.push(`M${x} ${y}h${w}v${h}h${-w}Z`);
|
|
28
|
+
},
|
|
29
|
+
fillRect(x, y, w, h) {
|
|
30
|
+
this.rect(x, y, w, h);
|
|
31
|
+
},
|
|
32
|
+
strokeRect(x, y, w, h) {
|
|
33
|
+
this.rect(x, y, w, h);
|
|
34
|
+
},
|
|
35
|
+
moveTo(x, y) {
|
|
36
|
+
parts.push(`M${x} ${y}`);
|
|
37
|
+
},
|
|
38
|
+
lineTo(x, y) {
|
|
39
|
+
parts.push(`L${x} ${y}`);
|
|
40
|
+
},
|
|
41
|
+
bezierCurveTo(c1x, c1y, c2x, c2y, x, y) {
|
|
42
|
+
parts.push(`C${c1x} ${c1y} ${c2x} ${c2y} ${x} ${y}`);
|
|
43
|
+
},
|
|
44
|
+
arc(x, y, r, start, end) {
|
|
45
|
+
this.ellipse(x, y, r, r, 0, start, end);
|
|
46
|
+
},
|
|
47
|
+
ellipse(x, y, rx, ry, _rotation, start, end) {
|
|
48
|
+
for (let k = 0; k <= ELLIPSE_SEGMENTS; k++) {
|
|
49
|
+
const a = start + ((end - start) * k) / ELLIPSE_SEGMENTS;
|
|
50
|
+
parts.push(`L${x + rx * Math.cos(a)} ${y + ry * Math.sin(a)}`);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
setLineDash(segments) {
|
|
54
|
+
dash = segments;
|
|
55
|
+
},
|
|
56
|
+
closePath() {
|
|
57
|
+
parts.push('Z');
|
|
58
|
+
},
|
|
59
|
+
fill() { },
|
|
60
|
+
stroke() {
|
|
61
|
+
strokedWidth = Math.max(strokedWidth, this.lineWidth);
|
|
62
|
+
if (dash.length > 0) {
|
|
63
|
+
strokedDash = dash;
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
ctx,
|
|
69
|
+
get d() {
|
|
70
|
+
return parts.join('');
|
|
71
|
+
},
|
|
72
|
+
/** The widest stroke traced, 0 for a shape that only fills. */
|
|
73
|
+
get lineWidth() {
|
|
74
|
+
return strokedWidth;
|
|
75
|
+
},
|
|
76
|
+
get dash() {
|
|
77
|
+
return strokedDash.length > 0 ? strokedDash.join(' ') : undefined;
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { BlockClipResult } from '../blockClipUtils.ts';
|
|
2
|
+
import type { RenderBlock } from '../renderBlock.ts';
|
|
3
|
+
import type { ColorChannel } from './markRamp.ts';
|
|
4
|
+
import type { RowChannel, RowParams } from './rowLane.ts';
|
|
5
|
+
import type { MarkFrame, MarkColorScale, MarkShape } from './types.ts';
|
|
6
|
+
import type { MarkValueScale } from './valueScale.ts';
|
|
7
|
+
/**
|
|
8
|
+
* The `point` shape's channels: a glyph per instance at the centre of
|
|
9
|
+
* `x`..`x2`, on the `y` scale.
|
|
10
|
+
*/
|
|
11
|
+
export interface PointChannels extends ColorChannel, RowChannel {
|
|
12
|
+
x: Uint32Array;
|
|
13
|
+
x2: Uint32Array;
|
|
14
|
+
y: Float32Array;
|
|
15
|
+
glyph: Uint8Array;
|
|
16
|
+
count: number;
|
|
17
|
+
}
|
|
18
|
+
/** What the point and rule shapes share: a value scale, rows and a colour scale. */
|
|
19
|
+
export interface ValuedMarkParams extends RowParams, MarkValueScale {
|
|
20
|
+
/** The quantitative colour scale, for a mark whose colour is a ramp or a threshold. */
|
|
21
|
+
colorScale?: MarkColorScale;
|
|
22
|
+
/**
|
|
23
|
+
* How far inside the plot the value range ends, so a mark at a domain
|
|
24
|
+
* endpoint draws whole — `glyphPaint`'s `pointInsetPx`, and the same number
|
|
25
|
+
* the display's axis takes as its offset. Absent centres it on the edge.
|
|
26
|
+
*/
|
|
27
|
+
insetPx?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface PointParams extends ValuedMarkParams {
|
|
30
|
+
/** Glyph diameter in CSS px. */
|
|
31
|
+
diameterPx: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The point shader's uniforms, which the rule shape draws through too:
|
|
35
|
+
* `radiusPx` is half a glyph or half a rule's thickness.
|
|
36
|
+
*/
|
|
37
|
+
export declare function writePointUniforms(scratch: ArrayBuffer, clip: BlockClipResult, block: RenderBlock, frame: MarkFrame, params: ValuedMarkParams, shape: {
|
|
38
|
+
radiusPx: number;
|
|
39
|
+
rule: boolean;
|
|
40
|
+
minWidthPx: number;
|
|
41
|
+
}): void;
|
|
42
|
+
export declare const pointMark: MarkShape<PointChannels, PointParams>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { bpRangeXTuple } from '../blockClipUtils.js';
|
|
2
|
+
import { CappedPath, getDpr, makeBpMapper } from '../canvas2dUtils.js';
|
|
3
|
+
import * as shader from '../shaders/pointMark.generated.js';
|
|
4
|
+
import { pointRowYPx, pointYPx } from '../shaders/pointMark.js.generated.js';
|
|
5
|
+
import { rowBandTopPx } from '../shaders/rowTable.js.generated.js';
|
|
6
|
+
import { slangPass } from '../slangPass.js';
|
|
7
|
+
import { abgrToCssRgba } from './colorFill.js';
|
|
8
|
+
import { appendGlyph, glyphBox } from './glyphPaint.js';
|
|
9
|
+
import { inkAtPoint, nearestInk } from './markHit.js';
|
|
10
|
+
import { colorBits, paintColors, rampUniforms } from './markRamp.js';
|
|
11
|
+
import { valueWindow } from './nearestMarkHit.js';
|
|
12
|
+
import { bandHeightPx, rowColor, rowLane, rowSlot, rowTableKeys, rowTableTextures, } from './rowLane.js';
|
|
13
|
+
import { valueScaleUniforms } from './valueScale.js';
|
|
14
|
+
/**
|
|
15
|
+
* The point shader's uniforms, which the rule shape draws through too:
|
|
16
|
+
* `radiusPx` is half a glyph or half a rule's thickness.
|
|
17
|
+
*/
|
|
18
|
+
export function writePointUniforms(scratch, clip, block, frame, params, shape) {
|
|
19
|
+
shader.writeUniforms(scratch, {
|
|
20
|
+
bpRangeX: bpRangeXTuple(clip, block.reversed),
|
|
21
|
+
canvasHeight: frame.canvasHeight,
|
|
22
|
+
domainMin: params.domain[0],
|
|
23
|
+
domainMax: params.domain[1],
|
|
24
|
+
...valueScaleUniforms(params),
|
|
25
|
+
...rampUniforms(params.colorScale),
|
|
26
|
+
zero: 0,
|
|
27
|
+
// viewportWidth and radiusPx stay in CSS units to match canvasHeight:
|
|
28
|
+
// mixing a DPR-scaled radius with a CSS-scaled height draws vertically
|
|
29
|
+
// stretched ellipses on hi-DPI.
|
|
30
|
+
viewportWidth: clip.scissorW,
|
|
31
|
+
radiusPx: shape.radiusPx,
|
|
32
|
+
rowHeight: bandHeightPx(params, frame.canvasHeight),
|
|
33
|
+
rowOffsetPx: params.rowOffsetPx ?? 0,
|
|
34
|
+
reverse: params.reverse ? 1 : 0,
|
|
35
|
+
insetPx: params.insetPx ?? 0,
|
|
36
|
+
devicePixelRatio: getDpr(),
|
|
37
|
+
rowTableKeys: rowTableKeys(params),
|
|
38
|
+
rule: shape.rule ? 1 : 0,
|
|
39
|
+
minWidthPx: shape.minWidthPx,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export const pointMark = {
|
|
43
|
+
id: 'point',
|
|
44
|
+
pass: {
|
|
45
|
+
...slangPass({ id: 'point', mod: shader }),
|
|
46
|
+
pack: c => shader.packInstances({ ...c, color: colorBits(c, c.count), row: rowLane(c.row, c.count) }, c.count),
|
|
47
|
+
},
|
|
48
|
+
writeUniforms(scratch, clip, block, frame, params) {
|
|
49
|
+
writePointUniforms(scratch, clip, block, frame, params, {
|
|
50
|
+
radiusPx: params.diameterPx / 2,
|
|
51
|
+
rule: false,
|
|
52
|
+
minWidthPx: 0,
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
textures: rowTableTextures,
|
|
56
|
+
paintBlock(ctx, channels, block, frame, params) {
|
|
57
|
+
const { x, x2, y, glyph, row, count } = channels;
|
|
58
|
+
if (count === 0) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const color = paintColors(channels, count, params.colorScale);
|
|
62
|
+
const { diameterPx, domain, insetPx = 0, rowOffsetPx: top = 0, rowTable: table, } = params;
|
|
63
|
+
const reverse = params.reverse ? 1 : 0;
|
|
64
|
+
const band = bandHeightPx(params, frame.canvasHeight);
|
|
65
|
+
const domainMin = domain[0];
|
|
66
|
+
const domainMax = domain[1];
|
|
67
|
+
const { valueScaleType: st, valueSymlogConstant: c } = valueScaleUniforms(params);
|
|
68
|
+
const bpToPx = makeBpMapper(block);
|
|
69
|
+
let current = color[0];
|
|
70
|
+
ctx.fillStyle = abgrToCssRgba(current);
|
|
71
|
+
const path = new CappedPath(ctx, 'fill');
|
|
72
|
+
for (let i = 0; i < count; i++) {
|
|
73
|
+
const slot = rowSlot(row, i, table);
|
|
74
|
+
if (slot === undefined) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const abgr = rowColor(color[i], row, i, table);
|
|
78
|
+
if (abgr !== current) {
|
|
79
|
+
path.flush();
|
|
80
|
+
current = abgr;
|
|
81
|
+
ctx.fillStyle = abgrToCssRgba(abgr);
|
|
82
|
+
}
|
|
83
|
+
path.add();
|
|
84
|
+
const yPx = pointRowYPx(rowBandTopPx(top, band, slot), band, reverse, pointYPx(y[i], domainMin, domainMax, band, st, insetPx, c));
|
|
85
|
+
appendGlyph(ctx, glyph[i], (bpToPx(x[i]) + bpToPx(x2[i])) / 2, yPx, diameterPx);
|
|
86
|
+
}
|
|
87
|
+
path.flush();
|
|
88
|
+
},
|
|
89
|
+
// The box `appendGlyph` paints inside.
|
|
90
|
+
ink(channels, block, frame, params, i) {
|
|
91
|
+
const { x, x2, y, glyph, row } = channels;
|
|
92
|
+
const slot = rowSlot(row, i, params.rowTable);
|
|
93
|
+
if (slot === undefined) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
const { diameterPx, domain, insetPx = 0 } = params;
|
|
97
|
+
const bpToPx = makeBpMapper(block);
|
|
98
|
+
const band = bandHeightPx(params, frame.canvasHeight);
|
|
99
|
+
const { valueScaleType: st, valueSymlogConstant: c } = valueScaleUniforms(params);
|
|
100
|
+
const cy = pointRowYPx(rowBandTopPx(params.rowOffsetPx ?? 0, band, slot), band, params.reverse ? 1 : 0, pointYPx(y[i], domain[0], domain[1], band, st, insetPx, c));
|
|
101
|
+
return glyphBox(glyph[i], (bpToPx(x[i]) + bpToPx(x2[i])) / 2, cy, diameterPx);
|
|
102
|
+
},
|
|
103
|
+
// Its own rather than the one `ink` implies: a dense plot stacks glyphs
|
|
104
|
+
// whose boxes all contain the cursor at distance 0, and the one under the
|
|
105
|
+
// cursor is the nearest CENTRE, which no box can say.
|
|
106
|
+
hitNearest(channels, block, frame, params, xPx, yPx, candidates, maxDistSq) {
|
|
107
|
+
const { x, x2, y, row } = channels;
|
|
108
|
+
const bpToPx = makeBpMapper(block);
|
|
109
|
+
const { domain, insetPx = 0, rowOffsetPx: top = 0, rowTable: table, } = params;
|
|
110
|
+
const reverse = params.reverse ? 1 : 0;
|
|
111
|
+
const domainMin = domain[0];
|
|
112
|
+
const domainMax = domain[1];
|
|
113
|
+
const band = bandHeightPx(params, frame.canvasHeight);
|
|
114
|
+
const { valueScaleType: st, valueSymlogConstant: c } = valueScaleUniforms(params);
|
|
115
|
+
return nearestInk(candidates, maxDistSq, i => {
|
|
116
|
+
const slot = rowSlot(row, i, table);
|
|
117
|
+
if (slot === undefined) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
const cy = pointRowYPx(rowBandTopPx(top, band, slot), band, reverse, pointYPx(y[i], domainMin, domainMax, band, st, insetPx, c));
|
|
121
|
+
return inkAtPoint(xPx, yPx, (bpToPx(x[i]) + bpToPx(x2[i])) / 2, cy);
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
valueWindow,
|
|
125
|
+
};
|