@genome-spy/core 0.62.2 → 0.64.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.
Files changed (64) hide show
  1. package/dist/bundle/index.es.js +4568 -4301
  2. package/dist/bundle/index.js +371 -297
  3. package/dist/schema.json +84 -12
  4. package/dist/src/genomeSpy.d.ts +11 -0
  5. package/dist/src/genomeSpy.d.ts.map +1 -1
  6. package/dist/src/genomeSpy.js +98 -20
  7. package/dist/src/gl/includes/common.glsl.js +1 -1
  8. package/dist/src/gl/webGLHelper.d.ts +18 -14
  9. package/dist/src/gl/webGLHelper.d.ts.map +1 -1
  10. package/dist/src/gl/webGLHelper.js +65 -64
  11. package/dist/src/index.d.ts.map +1 -1
  12. package/dist/src/index.js +1 -0
  13. package/dist/src/marks/link.fragment.glsl.js +1 -1
  14. package/dist/src/marks/link.vertex.glsl.js +1 -1
  15. package/dist/src/marks/mark.d.ts +6 -1
  16. package/dist/src/marks/mark.d.ts.map +1 -1
  17. package/dist/src/marks/mark.js +10 -16
  18. package/dist/src/marks/point.fragment.glsl.js +1 -1
  19. package/dist/src/marks/point.vertex.glsl.js +1 -1
  20. package/dist/src/marks/rect.fragment.glsl.js +1 -1
  21. package/dist/src/marks/rect.vertex.glsl.js +1 -1
  22. package/dist/src/marks/rule.fragment.glsl.js +1 -1
  23. package/dist/src/marks/rule.vertex.glsl.js +1 -1
  24. package/dist/src/marks/text.d.ts.map +1 -1
  25. package/dist/src/marks/text.fragment.glsl.js +1 -1
  26. package/dist/src/marks/text.js +7 -15
  27. package/dist/src/marks/text.vertex.glsl.js +1 -1
  28. package/dist/src/selection/selection.d.ts +5 -0
  29. package/dist/src/selection/selection.d.ts.map +1 -1
  30. package/dist/src/selection/selection.js +43 -6
  31. package/dist/src/selection/selection.test.d.ts +2 -0
  32. package/dist/src/selection/selection.test.d.ts.map +1 -0
  33. package/dist/src/selection/selection.test.js +14 -0
  34. package/dist/src/spec/parameter.d.ts +28 -2
  35. package/dist/src/styles/{genome-spy.scss → genome-spy.css} +25 -21
  36. package/dist/src/styles/genome-spy.css.d.ts +1 -1
  37. package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
  38. package/dist/src/styles/genome-spy.css.js +264 -195
  39. package/dist/src/styles/update.sh +14 -4
  40. package/dist/src/types/embedApi.d.ts +16 -0
  41. package/dist/src/types/viewContext.d.ts +0 -2
  42. package/dist/src/utils/expression.d.ts +5 -0
  43. package/dist/src/utils/expression.d.ts.map +1 -1
  44. package/dist/src/utils/expression.js +37 -0
  45. package/dist/src/utils/interactionEvent.d.ts +18 -1
  46. package/dist/src/utils/interactionEvent.d.ts.map +1 -1
  47. package/dist/src/utils/interactionEvent.js +101 -1
  48. package/dist/src/utils/interactionEvent.test.d.ts +2 -0
  49. package/dist/src/utils/interactionEvent.test.d.ts.map +1 -0
  50. package/dist/src/utils/interactionEvent.test.js +35 -0
  51. package/dist/src/view/facetView.d.ts +1 -1
  52. package/dist/src/view/facetView.d.ts.map +1 -1
  53. package/dist/src/view/gridView/gridView.js +1 -1
  54. package/dist/src/view/renderingContext/bufferedViewRenderingContext.d.ts +32 -17
  55. package/dist/src/view/renderingContext/bufferedViewRenderingContext.d.ts.map +1 -1
  56. package/dist/src/view/renderingContext/bufferedViewRenderingContext.js +85 -39
  57. package/dist/src/view/renderingContext/simpleViewRenderingContext.d.ts.map +1 -1
  58. package/dist/src/view/renderingContext/simpleViewRenderingContext.js +5 -1
  59. package/dist/src/view/renderingContext/viewRenderingContext.d.ts +1 -0
  60. package/dist/src/view/renderingContext/viewRenderingContext.d.ts.map +1 -1
  61. package/dist/src/view/renderingContext/viewRenderingContext.js +4 -0
  62. package/dist/src/view/unitView.d.ts.map +1 -1
  63. package/dist/src/view/unitView.js +45 -2
  64. package/package.json +8 -8
package/dist/schema.json CHANGED
@@ -1654,6 +1654,15 @@
1654
1654
  }
1655
1655
  ]
1656
1656
  },
1657
+ "DomEventType": {
1658
+ "enum": [
1659
+ "click",
1660
+ "dblclick",
1661
+ "mouseover",
1662
+ "pointerover"
1663
+ ],
1664
+ "type": "string"
1665
+ },
1657
1666
  "DsvDataFormat": {
1658
1667
  "additionalProperties": false,
1659
1668
  "properties": {
@@ -2107,6 +2116,23 @@
2107
2116
  },
2108
2117
  "type": "object"
2109
2118
  },
2119
+ "EventConfig": {
2120
+ "additionalProperties": false,
2121
+ "properties": {
2122
+ "filter": {
2123
+ "description": "An optional filter expression to further filter events of the specified type. The expression can only refer to the event object as `event`, and should evaluate to a boolean value indicating whether to include the event. No other data or parameters are in scope.",
2124
+ "type": "string"
2125
+ },
2126
+ "type": {
2127
+ "$ref": "#/definitions/DomEventType",
2128
+ "description": "The type of event to listen to. For example, `\"click\"` or `\"mouseover\"`."
2129
+ }
2130
+ },
2131
+ "required": [
2132
+ "type"
2133
+ ],
2134
+ "type": "object"
2135
+ },
2110
2136
  "ExprDef": {
2111
2137
  "additionalProperties": false,
2112
2138
  "properties": {
@@ -3715,6 +3741,23 @@
3715
3741
  "IntervalSelectionConfig": {
3716
3742
  "additionalProperties": false,
3717
3743
  "properties": {
3744
+ "clear": {
3745
+ "anyOf": [
3746
+ {
3747
+ "$ref": "#/definitions/DomEventType"
3748
+ },
3749
+ {
3750
+ "$ref": "#/definitions/EventConfig"
3751
+ },
3752
+ {
3753
+ "type": "string"
3754
+ },
3755
+ {
3756
+ "type": "boolean"
3757
+ }
3758
+ ],
3759
+ "description": "A string or object that defines the events that should clear the selection.\n\n__Default value:__ `\"dblclick\"`"
3760
+ },
3718
3761
  "encodings": {
3719
3762
  "description": "An array of encoding channels that define the interval selection.",
3720
3763
  "items": {
@@ -3727,16 +3770,22 @@
3727
3770
  "description": "Interval selections display a rectangle mark to show the selected range. Use the `mark` property to adjust the appearance of this rectangle."
3728
3771
  },
3729
3772
  "on": {
3730
- "enum": [
3731
- "click",
3732
- "mouseover",
3733
- "pointerover"
3773
+ "anyOf": [
3774
+ {
3775
+ "$ref": "#/definitions/DomEventType"
3776
+ },
3777
+ {
3778
+ "$ref": "#/definitions/EventConfig"
3779
+ },
3780
+ {
3781
+ "type": "string"
3782
+ }
3734
3783
  ],
3735
- "type": "string"
3784
+ "description": "A string or object that defines the events to which the selection should listen."
3736
3785
  },
3737
3786
  "type": {
3738
3787
  "const": "interval",
3739
- "description": "Determines the default event processing and data query for the selection. Vega-Lite currently supports two selection types:\n\n- `\"point\"` -- to select multiple discrete data values; the first value is selected on `click` and additional values toggled on shift-click.\n- `\"interval\"` -- to select a continuous range of data values on `drag`.",
3788
+ "description": "The selection type.\n\n- `\"point\"` -- to select multiple discrete data values; the first value is selected on `click` and additional values toggled on shift-click.\n- `\"interval\"` -- to select a continuous range of data values on `drag`.",
3740
3789
  "type": "string"
3741
3790
  }
3742
3791
  },
@@ -4978,13 +5027,36 @@
4978
5027
  "PointSelectionConfig": {
4979
5028
  "additionalProperties": false,
4980
5029
  "properties": {
5030
+ "clear": {
5031
+ "anyOf": [
5032
+ {
5033
+ "$ref": "#/definitions/DomEventType"
5034
+ },
5035
+ {
5036
+ "$ref": "#/definitions/EventConfig"
5037
+ },
5038
+ {
5039
+ "type": "string"
5040
+ },
5041
+ {
5042
+ "type": "boolean"
5043
+ }
5044
+ ],
5045
+ "description": "A string or object that defines the events that should clear the selection.\n\n__Default value:__ `\"dblclick\"`"
5046
+ },
4981
5047
  "on": {
4982
- "enum": [
4983
- "click",
4984
- "mouseover",
4985
- "pointerover"
5048
+ "anyOf": [
5049
+ {
5050
+ "$ref": "#/definitions/DomEventType"
5051
+ },
5052
+ {
5053
+ "$ref": "#/definitions/EventConfig"
5054
+ },
5055
+ {
5056
+ "type": "string"
5057
+ }
4986
5058
  ],
4987
- "type": "string"
5059
+ "description": "A string or object that defines the events to which the selection should listen."
4988
5060
  },
4989
5061
  "toggle": {
4990
5062
  "description": "Controls whether data values should be toggled (inserted or removed from a point selection) when clicking with the shift key pressed.\n\n- `true` -- additional values can be selected by shift-clicking.\n- `false` -- only a single value can be selected at a time.\n\n__Default value:__ `true`",
@@ -4992,7 +5064,7 @@
4992
5064
  },
4993
5065
  "type": {
4994
5066
  "const": "point",
4995
- "description": "Determines the default event processing and data query for the selection. Vega-Lite currently supports two selection types:\n\n- `\"point\"` -- to select multiple discrete data values; the first value is selected on `click` and additional values toggled on shift-click.\n- `\"interval\"` -- to select a continuous range of data values on `drag`.",
5067
+ "description": "The selection type.\n\n- `\"point\"` -- to select multiple discrete data values; the first value is selected on `click` and additional values toggled on shift-click.\n- `\"interval\"` -- to select a continuous range of data values on `drag`.",
4996
5068
  "type": "string"
4997
5069
  }
4998
5070
  },
@@ -84,6 +84,7 @@ export default class GenomeSpy {
84
84
  _inputBindingContainer: HTMLElement;
85
85
  /** @type {Point} */
86
86
  _mouseDownCoords: Point;
87
+ dpr: number;
87
88
  /**
88
89
  *
89
90
  * @param {(name: string) => any[]} provider
@@ -141,6 +142,16 @@ export default class GenomeSpy {
141
142
  * @template T
142
143
  */
143
144
  updateTooltip<T>(datum: T, converter?: (arg0: T) => Promise<string | HTMLElement | import("lit").TemplateResult>): void;
145
+ /**
146
+ * Returns a PNG data URL of the current canvas.
147
+ *
148
+ * @param {number} [logicalWidth] defaults to canvas width
149
+ * @param {number} [logicalHeight] defaults to canvas height
150
+ * @param {number} [devicePixelRatio] defaults to window.devicePixelRatio
151
+ * @param {string} [clearColor] null for transparent
152
+ * @returns A PNG data Url
153
+ */
154
+ exportCanvas(logicalWidth?: number, logicalHeight?: number, devicePixelRatio?: number, clearColor?: string): string;
144
155
  computeLayout(): void;
145
156
  renderAll(): void;
146
157
  renderPickingFramebuffer(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"genomeSpy.d.ts","sourceRoot":"","sources":["../../src/genomeSpy.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"genomeSpy.d.ts","sourceRoot":"","sources":["../../src/genomeSpy.js"],"names":[],"mappings":"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"}
@@ -13,7 +13,10 @@ import {
13
13
  } from "./view/viewUtils.js";
14
14
  import UnitView from "./view/unitView.js";
15
15
 
16
- import WebGLHelper from "./gl/webGLHelper.js";
16
+ import WebGLHelper, {
17
+ framebufferToDataUrl,
18
+ readPickingPixel,
19
+ } from "./gl/webGLHelper.js";
17
20
  import Rectangle from "./view/layout/rectangle.js";
18
21
  import BufferedViewRenderingContext from "./view/renderingContext/bufferedViewRenderingContext.js";
19
22
  import CompositeViewRenderingContext from "./view/renderingContext/compositeViewRenderingContext.js";
@@ -35,6 +38,7 @@ import { VIEW_ROOT_NAME, ViewFactory } from "./view/viewFactory.js";
35
38
  import { reconfigureScales } from "./view/scaleResolution.js";
36
39
  import createBindingInputs from "./utils/inputBinding.js";
37
40
  import { isStillZooming } from "./view/zoom.js";
41
+ import { createFramebufferInfo } from "twgl.js";
38
42
 
39
43
  /**
40
44
  * Events that are broadcasted to all views.
@@ -148,6 +152,8 @@ export default class GenomeSpy {
148
152
 
149
153
  /** @type {Point} */
150
154
  this._mouseDownCoords = undefined;
155
+
156
+ this.dpr = window.devicePixelRatio;
151
157
  }
152
158
 
153
159
  get #canvasWrapper() {
@@ -307,12 +313,13 @@ export default class GenomeSpy {
307
313
  #setupDpr() {
308
314
  const dprSetter = this.viewRoot.paramMediator.allocateSetter(
309
315
  "devicePixelRatio",
310
- window.devicePixelRatio
316
+ this.dpr
311
317
  );
312
318
 
313
319
  const resizeCallback = () => {
314
320
  this._glHelper.invalidateSize();
315
- dprSetter(window.devicePixelRatio);
321
+ this.dpr = window.devicePixelRatio;
322
+ dprSetter(this.dpr);
316
323
  this.computeLayout();
317
324
  // Render immediately, without RAF
318
325
  this.renderAll();
@@ -368,7 +375,6 @@ export default class GenomeSpy {
368
375
  this.viewRoot
369
376
  ? calculateCanvasSize(this.viewRoot)
370
377
  : { width: undefined, height: undefined },
371
- this.spec.background,
372
378
  { powerPreference: this.options.powerPreference ?? "default" }
373
379
  );
374
380
 
@@ -444,10 +450,6 @@ export default class GenomeSpy {
444
450
  genomeStore: this.genomeStore,
445
451
  fontManager: new BmFontManager(this._glHelper),
446
452
 
447
- get devicePixelRatio() {
448
- return self._glHelper.dpr;
449
- },
450
-
451
453
  requestLayoutReflow: () => {
452
454
  // placeholder
453
455
  },
@@ -877,13 +879,15 @@ export default class GenomeSpy {
877
879
  * @param {number} y
878
880
  */
879
881
  _handlePicking(x, y) {
880
- const pixelValue = this._glHelper.readPickingPixel(x, y);
882
+ const dpr = this.dpr;
883
+ const pp = readPickingPixel(
884
+ this._glHelper.gl,
885
+ this._glHelper._pickingBufferInfo,
886
+ x * dpr,
887
+ y * dpr
888
+ );
881
889
 
882
- const uniqueId =
883
- pixelValue[0] |
884
- (pixelValue[1] << 8) |
885
- (pixelValue[2] << 16) |
886
- (pixelValue[3] << 24);
890
+ const uniqueId = pp[0] | (pp[1] << 8) | (pp[2] << 16) | (pp[3] << 24);
887
891
 
888
892
  if (uniqueId == 0) {
889
893
  this._currentHover = null;
@@ -964,6 +968,72 @@ export default class GenomeSpy {
964
968
  }
965
969
  }
966
970
 
971
+ /**
972
+ * Returns a PNG data URL of the current canvas.
973
+ *
974
+ * @param {number} [logicalWidth] defaults to canvas width
975
+ * @param {number} [logicalHeight] defaults to canvas height
976
+ * @param {number} [devicePixelRatio] defaults to window.devicePixelRatio
977
+ * @param {string} [clearColor] null for transparent
978
+ * @returns A PNG data Url
979
+ */
980
+ exportCanvas(
981
+ logicalWidth,
982
+ logicalHeight,
983
+ devicePixelRatio,
984
+ clearColor = "white"
985
+ ) {
986
+ const helper = this._glHelper;
987
+
988
+ logicalWidth ??= helper.getLogicalCanvasSize().width;
989
+ logicalHeight ??= helper.getLogicalCanvasSize().height;
990
+ devicePixelRatio ??= window.devicePixelRatio ?? 1;
991
+
992
+ const gl = helper.gl;
993
+
994
+ const width = Math.floor(logicalWidth * devicePixelRatio);
995
+ const height = Math.floor(logicalHeight * devicePixelRatio);
996
+
997
+ const framebufferInfo = createFramebufferInfo(
998
+ gl,
999
+ [
1000
+ {
1001
+ format: gl.RGBA,
1002
+ type: gl.UNSIGNED_BYTE,
1003
+ minMag: gl.LINEAR,
1004
+ wrap: gl.CLAMP_TO_EDGE,
1005
+ },
1006
+ ],
1007
+ width,
1008
+ height
1009
+ );
1010
+
1011
+ const renderingContext = new BufferedViewRenderingContext(
1012
+ { picking: false },
1013
+ {
1014
+ webGLHelper: this._glHelper,
1015
+ canvasSize: { width: logicalWidth, height: logicalHeight },
1016
+ devicePixelRatio,
1017
+ clearColor,
1018
+ framebufferInfo,
1019
+ }
1020
+ );
1021
+
1022
+ this.viewRoot.render(
1023
+ renderingContext,
1024
+ Rectangle.create(0, 0, logicalWidth, logicalHeight)
1025
+ );
1026
+ renderingContext.render();
1027
+
1028
+ const pngUrl = framebufferToDataUrl(gl, framebufferInfo, "image/png");
1029
+
1030
+ // Clean up
1031
+ this.computeLayout();
1032
+ this.renderAll();
1033
+
1034
+ return pngUrl;
1035
+ }
1036
+
967
1037
  computeLayout() {
968
1038
  const root = this.viewRoot;
969
1039
  if (!root) {
@@ -982,17 +1052,25 @@ export default class GenomeSpy {
982
1052
  return;
983
1053
  }
984
1054
 
1055
+ const commonOptions = {
1056
+ webGLHelper: this._glHelper,
1057
+ canvasSize,
1058
+ devicePixelRatio: window.devicePixelRatio ?? 1,
1059
+ };
1060
+
985
1061
  this._renderingContext = new BufferedViewRenderingContext(
1062
+ { picking: false },
986
1063
  {
987
- picking: false,
988
- },
989
- this._glHelper
1064
+ ...commonOptions,
1065
+ clearColor: this.spec.background,
1066
+ }
990
1067
  );
991
1068
  this._pickingContext = new BufferedViewRenderingContext(
1069
+ { picking: true },
992
1070
  {
993
- picking: true,
994
- },
995
- this._glHelper
1071
+ ...commonOptions,
1072
+ framebufferInfo: this._glHelper._pickingBufferInfo,
1073
+ }
996
1074
  );
997
1075
 
998
1076
  root.render(
@@ -1,2 +1,2 @@
1
- const shader = "#define PI 3.141593\nuniform View{mediump vec2 uViewOffset;mediump vec2 uViewScale;mediump vec2 uViewportSize;lowp float uDevicePixelRatio;lowp float uViewOpacity;bool uPickingEnabled;};/***Maps a coordinate on the unit scale to a normalized device coordinate.*(0,0)is at the bottom left corner.*/vec4 unitToNdc(vec2 coord){return vec4((coord*uViewScale+uViewOffset)*2.0-1.0,0.0,1.0);}vec4 unitToNdc(float x,float y){return unitToNdc(vec2(x,y));}vec4 pixelsToNdc(vec2 coord){return unitToNdc(coord/uViewportSize);}vec4 pixelsToNdc(float x,float y){return pixelsToNdc(vec2(x,y));}float linearstep(float edge0,float edge1,float x){return clamp((x-edge0)/(edge1-edge0),0.0,1.0);}bool isEmptyBinarySearchTexture(highp usampler2D s){return textureSize(s,0).x==1&&texelFetch(s,ivec2(0,0),0).r==0u;}bool binarySearchTexture(highp usampler2D s,uint value){int texSize=textureSize(s,0).x;int left=0;int right=texSize-1;while(left<=right){int mid=left+(right-left)/2;uint midValue=texelFetch(s,ivec2(mid,0),0).r;if(midValue==value){return true;}if(midValue<value){left=mid+1;}else{right=mid-1;}}return false;}/***Calculates a gamma for antialiasing opacity based on the color.*/float getGammaForColor(vec3 rgb){return mix(1.25,0.75,smoothstep(0.0,1.0,dot(rgb,vec3(0.299,0.587,0.114))));}/***Specialized linearstep for doing antialiasing*/float distanceToRatio(float d){return clamp(d*uDevicePixelRatio+0.5,0.0,1.0);}vec4 distanceToColor(float d,vec4 fill,vec4 stroke,vec4 background,float halfStrokeWidth){if(halfStrokeWidth>0.0){float sd=abs(d)-halfStrokeWidth;return mix(stroke,d<=0.0 ? fill : background,distanceToRatio(sd));}else{return mix(background,fill,distanceToRatio(-d));}}";
1
+ const shader = "#define PI 3.141593\nuniform View{mediump vec2 uViewOffset;mediump vec2 uViewScale;mediump vec2 uViewportSize;lowp float uDevicePixelRatio;lowp float uViewOpacity;bool uPickingEnabled;};/***Maps a coordinate on the unit scale to a normalized device coordinate.*(0,0)is at the bottom left corner.*/vec4 unitToNdc(vec2 coord){return vec4((coord*uViewScale+uViewOffset)*2.0-1.0,0.0,1.0);}vec4 unitToNdc(float x,float y){return unitToNdc(vec2(x,y));}vec4 pixelsToNdc(vec2 coord){return unitToNdc(coord/uViewportSize);}vec4 pixelsToNdc(float x,float y){return pixelsToNdc(vec2(x,y));}float linearstep(float edge0,float edge1,float x){return clamp((x-edge0)/(edge1-edge0),0.0,1.0);}bool isEmptyBinarySearchTexture(highp usampler2D s){return textureSize(s,0).x==1&&texelFetch(s,ivec2(0,0),0).r==0u;}bool binarySearchTexture(highp usampler2D s,uint value){int texSize=textureSize(s,0).x;if(texSize==1&&texelFetch(s,ivec2(0,0),0).r==0u){return false;}int left=0;int right=texSize-1;while(left<=right){int mid=left+(right-left)/2;uint midValue=texelFetch(s,ivec2(mid,0),0).r;if(midValue==value){return true;}if(midValue<value){left=mid+1;}else{right=mid-1;}}return false;}/***Calculates a gamma for antialiasing opacity based on the color.*/float getGammaForColor(vec3 rgb){return mix(1.25,0.75,smoothstep(0.0,1.0,dot(rgb,vec3(0.299,0.587,0.114))));}/***Specialized linearstep for doing antialiasing*/float distanceToRatio(float d){return clamp(d*uDevicePixelRatio+0.5,0.0,1.0);}vec4 distanceToColor(float d,vec4 fill,vec4 stroke,vec4 background,float halfStrokeWidth){if(halfStrokeWidth>0.0){float sd=abs(d)-halfStrokeWidth;return mix(stroke,d<=0.0 ? fill : background,distanceToRatio(sd));}else{return mix(background,fill,distanceToRatio(-d));}}";
2
2
  export default shader;
@@ -17,6 +17,21 @@ export function createProgram(gl: WebGL2RenderingContext, vertexShader: WebGLSha
17
17
  * @param {WebGLTexture} [texture]
18
18
  */
19
19
  export function createOrUpdateTexture(gl: WebGLRenderingContext, options: Omit<import("twgl.js").TextureOptions, "src">, src: number[] | ArrayBufferView, texture?: WebGLTexture): WebGLTexture;
20
+ /**
21
+ *
22
+ * @param {WebGL2RenderingContext} gl
23
+ * @param {import("twgl.js").FramebufferInfo} framebufferInfo
24
+ * @param {number} x
25
+ * @param {number} y
26
+ */
27
+ export function readPickingPixel(gl: WebGL2RenderingContext, framebufferInfo: import("twgl.js").FramebufferInfo, x: number, y: number): Uint8Array<ArrayBuffer>;
28
+ /**
29
+ *
30
+ * @param {WebGL2RenderingContext} gl
31
+ * @param {import("twgl.js").FramebufferInfo} framebufferInfo
32
+ * @param {string} [type]
33
+ */
34
+ export function framebufferToDataUrl(gl: WebGL2RenderingContext, framebufferInfo: import("twgl.js").FramebufferInfo, type?: string): string;
20
35
  export default class WebGLHelper {
21
36
  /**
22
37
  *
@@ -24,13 +39,12 @@ export default class WebGLHelper {
24
39
  * @param {() => {width: number, height: number}} [sizeSource]
25
40
  * A function that returns the content size. If a dimension is undefined,
26
41
  * the canvas fills the container, otherwise the canvas is adjusted to the content size.
27
- * @param {string} [clearColor]
28
42
  * @param {WebGLContextAttributes} [webglContextAttributes]
29
43
  */
30
44
  constructor(container: HTMLElement, sizeSource?: () => {
31
45
  width: number;
32
46
  height: number;
33
- }, clearColor?: string, webglContextAttributes?: WebGLContextAttributes);
47
+ }, webglContextAttributes?: WebGLContextAttributes);
34
48
  _container: HTMLElement;
35
49
  _sizeSource: () => {
36
50
  width: any;
@@ -49,15 +63,11 @@ export default class WebGLHelper {
49
63
  /** @type {import("twgl.js").AttachmentOptions[]} */
50
64
  _pickingAttachmentOptions: import("twgl.js").AttachmentOptions[];
51
65
  _pickingBufferInfo: import("twgl.js").FramebufferInfo;
52
- /** @type {[number, number, number, number]} */
53
- _clearColor: [number, number, number, number];
54
66
  invalidateSize(): void;
55
67
  _logicalCanvasSize: {
56
68
  width: any;
57
69
  height: any;
58
70
  };
59
- _updateDpr(): void;
60
- dpr: number;
61
71
  /**
62
72
  * Compiles and caches a shader. The shader source is used as a cache key.
63
73
  *
@@ -80,19 +90,13 @@ export default class WebGLHelper {
80
90
  height: number;
81
91
  };
82
92
  /**
83
- * Returns the canvas size in logical pixels (without devicePixelRatio correction)
93
+ * Returns the size of the canvas canvas container size in logical pixels,
94
+ * without devicePixelRatio correction.
84
95
  */
85
96
  getLogicalCanvasSize(): {
86
97
  width: any;
87
98
  height: any;
88
99
  };
89
- /**
90
- *
91
- * @param {number} x
92
- * @param {number} y
93
- */
94
- readPickingPixel(x: number, y: number): Uint8Array<ArrayBuffer>;
95
- clearAll(): void;
96
100
  /**
97
101
  * Creates textures for color schemes and discrete/discretizing ranges.
98
102
  * N.B. Discrete range textures need domain. Thus, this cannot be called
@@ -1 +1 @@
1
- {"version":3,"file":"webGLHelper.d.ts","sourceRoot":"","sources":["../../../src/gl/webGLHelper.js"],"names":[],"mappings":"AA2cA;;;;GAIG;AACH,kCAJW,sBAAsB,gBACtB,WAAW,kBACX,WAAW;;;;;;EA8CrB;AAED;;;;;GAKG;AACH,0CALW,qBAAqB,WACrB,IAAI,CAAC,OAAO,SAAS,EAAE,cAAc,EAAE,KAAK,CAAC,OAC7C,MAAM,EAAE,GAAG,eAAe,YAC1B,YAAY,gBAYtB;AA/eD;IACI;;;;;;;;OAQG;IACH,uBAPW,WAAW,eACX,MAAM;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,eAGrC,MAAM,2BACN,sBAAsB,EA8FhC;IAtFG,wBAA2B;IAC3B;;;MAKO;IAEP,uCAAuC;IACvC,cADW,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CACN;IAE7B,6EAA6E;IAC7E,eADW,OAAO,CAAC,OAAO,qBAAqB,EAAE,SAAS,EAAE,YAAY,CAAC,CACvC;IAElC;;OAEG;IACH,mBAFU,OAAO,CAAC,OAAO,4BAA4B,EAAE,mBAAmB,EAAE,YAAY,CAAC,CAEnD;IAwCtC,0BAAoB;IACpB,2BAAY;IAGZ,oDAAoD;IACpD,2BADW,OAAO,SAAS,EAAE,iBAAiB,EAAE,CAQ/C;IACD,sDAGC;IAOD,+CAA+C;IAC/C,aADW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CACZ;IAOnC,uBAIC;IAHG;;;MAAmC;IAKvC,mBAEC;IADG,YAAkC;IAGtC;;;;;OAKG;IACH,oBAHW,MAAM,QACN,MAAM,GAAG,MAAM,EAAE,eA2B3B;IAED,iBAcC;IAED,iBAEC;IAED;;;;OAIG;IACH,oCAFW;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;;;MAQ3C;IAED;;OAEG;IACH;;;MAuBC;IAED;;;;OAIG;IACH,oBAHW,MAAM,KACN,MAAM,2BAwBhB;IAED,iBAOC;IAED;;;;;;;;;OASG;IACH,+BAHW,OAAO,4BAA4B,EAAE,OAAO,WAC5C,OAAO,QA0GjB;IAED;;OAEG;IACH,kCAFW,OAAO,4BAA4B,EAAE,mBAAmB,0BA+BlE;CACJ"}
1
+ {"version":3,"file":"webGLHelper.d.ts","sourceRoot":"","sources":["../../../src/gl/webGLHelper.js"],"names":[],"mappings":"AAwZA;;;;GAIG;AACH,kCAJW,sBAAsB,gBACtB,WAAW,kBACX,WAAW;;;;;;EA8CrB;AAED;;;;;GAKG;AACH,0CALW,qBAAqB,WACrB,IAAI,CAAC,OAAO,SAAS,EAAE,cAAc,EAAE,KAAK,CAAC,OAC7C,MAAM,EAAE,GAAG,eAAe,YAC1B,YAAY,gBAYtB;AAED;;;;;;GAMG;AACH,qCALW,sBAAsB,mBACtB,OAAO,SAAS,EAAE,eAAe,KACjC,MAAM,KACN,MAAM,2BAWhB;AAED;;;;;GAKG;AACH,yCAJW,sBAAsB,mBACtB,OAAO,SAAS,EAAE,eAAe,SACjC,MAAM,UA4BhB;AAjfD;IACI;;;;;;;OAOG;IACH,uBANW,WAAW,eACX,MAAM;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,2BAGrC,sBAAsB,EAsFhC;IAnFG,wBAA2B;IAC3B;;;MAKO;IAEP,uCAAuC;IACvC,cADW,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CACN;IAE7B,6EAA6E;IAC7E,eADW,OAAO,CAAC,OAAO,qBAAqB,EAAE,SAAS,EAAE,YAAY,CAAC,CACvC;IAElC;;OAEG;IACH,mBAFU,OAAO,CAAC,OAAO,4BAA4B,EAAE,mBAAmB,EAAE,YAAY,CAAC,CAEnD;IA8CtC,0BAAoB;IACpB,2BAAY;IAGZ,oDAAoD;IACpD,2BADW,OAAO,SAAS,EAAE,iBAAiB,EAAE,CAQ/C;IACD,sDAGC;IAML,uBAGC;IAFG;;;MAAmC;IAIvC;;;;;OAKG;IACH,oBAHW,MAAM,QACN,MAAM,GAAG,MAAM,EAAE,eA2B3B;IAED,iBAcC;IAED,iBAEC;IAED;;;;OAIG;IACH,oCAFW;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;;;MAS3C;IAED;;;OAGG;IACH;;;MAuBC;IAED;;;;;;;;;OASG;IACH,+BAHW,OAAO,4BAA4B,EAAE,OAAO,WAC5C,OAAO,QA0GjB;IAED;;OAEG;IACH,kCAFW,OAAO,4BAA4B,EAAE,mBAAmB,0BA+BlE;CACJ"}
@@ -26,7 +26,6 @@ import {
26
26
  isColorChannel,
27
27
  isDiscreteChannel,
28
28
  } from "../encoder/encoder.js";
29
- import { color } from "d3-color";
30
29
  import { isMultiPointSelection } from "../selection/selection.js";
31
30
 
32
31
  export default class WebGLHelper {
@@ -36,15 +35,9 @@ export default class WebGLHelper {
36
35
  * @param {() => {width: number, height: number}} [sizeSource]
37
36
  * A function that returns the content size. If a dimension is undefined,
38
37
  * the canvas fills the container, otherwise the canvas is adjusted to the content size.
39
- * @param {string} [clearColor]
40
38
  * @param {WebGLContextAttributes} [webglContextAttributes]
41
39
  */
42
- constructor(
43
- container,
44
- sizeSource,
45
- clearColor,
46
- webglContextAttributes = {}
47
- ) {
40
+ constructor(container, sizeSource, webglContextAttributes = {}) {
48
41
  this._container = container;
49
42
  this._sizeSource =
50
43
  sizeSource ??
@@ -97,7 +90,13 @@ export default class WebGLHelper {
97
90
 
98
91
  addExtensionsToContext(gl);
99
92
 
100
- // TODO: view background: https://vega.github.io/vega-lite/docs/spec.html#view-background
93
+ // Make flat shading fast. All flat vertices have the same values, so
94
+ // it doesn't matter which one is the provoking vertex.
95
+ // https://registry.khronos.org/webgl/extensions/WEBGL_provoking_vertex/
96
+ const epv = gl.getExtension("WEBGL_provoking_vertex");
97
+ if (epv) {
98
+ epv.provokingVertexWEBGL(epv.FIRST_VERTEX_CONVENTION_WEBGL);
99
+ }
101
100
 
102
101
  // Always use pre-multiplied alpha
103
102
  gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
@@ -122,27 +121,13 @@ export default class WebGLHelper {
122
121
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
123
122
 
124
123
  this.adjustGl();
125
-
126
- this._updateDpr();
127
-
128
- /** @type {[number, number, number, number]} */
129
- this._clearColor = [0, 0, 0, 0];
130
- if (clearColor) {
131
- const c = color(clearColor).rgb();
132
- this._clearColor = [c.r / 255, c.g / 255, c.b / 255, c.opacity];
133
- }
134
124
  }
135
125
 
136
126
  invalidateSize() {
137
127
  this._logicalCanvasSize = undefined;
138
- this._updateDpr();
139
128
  this.adjustGl();
140
129
  }
141
130
 
142
- _updateDpr() {
143
- this.dpr = window.devicePixelRatio;
144
- }
145
-
146
131
  /**
147
132
  * Compiles and caches a shader. The shader source is used as a cache key.
148
133
  *
@@ -202,15 +187,17 @@ export default class WebGLHelper {
202
187
  * @param {{ width: number, height: number }} [logicalSize]
203
188
  */
204
189
  getPhysicalCanvasSize(logicalSize) {
190
+ const dpr = window.devicePixelRatio ?? 1;
205
191
  logicalSize = logicalSize || this.getLogicalCanvasSize();
206
192
  return {
207
- width: logicalSize.width * this.dpr,
208
- height: logicalSize.height * this.dpr,
193
+ width: logicalSize.width * dpr,
194
+ height: logicalSize.height * dpr,
209
195
  };
210
196
  }
211
197
 
212
198
  /**
213
- * Returns the canvas size in logical pixels (without devicePixelRatio correction)
199
+ * Returns the size of the canvas canvas container size in logical pixels,
200
+ * without devicePixelRatio correction.
214
201
  */
215
202
  getLogicalCanvasSize() {
216
203
  if (this._logicalCanvasSize) {
@@ -237,44 +224,6 @@ export default class WebGLHelper {
237
224
  return this._logicalCanvasSize;
238
225
  }
239
226
 
240
- /**
241
- *
242
- * @param {number} x
243
- * @param {number} y
244
- */
245
- readPickingPixel(x, y) {
246
- const gl = this.gl;
247
-
248
- x *= this.dpr;
249
- y *= this.dpr;
250
-
251
- const height = this.getPhysicalCanvasSize().height;
252
-
253
- const pixel = new Uint8Array(4);
254
- gl.bindFramebuffer(gl.FRAMEBUFFER, this._pickingBufferInfo.framebuffer);
255
- gl.readPixels(
256
- x,
257
- height - y - 1,
258
- 1,
259
- 1,
260
- gl.RGBA,
261
- gl.UNSIGNED_BYTE,
262
- pixel
263
- );
264
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
265
-
266
- return pixel;
267
- }
268
-
269
- clearAll() {
270
- const gl = this.gl;
271
- const { width, height } = this.getPhysicalCanvasSize();
272
- gl.viewport(0, 0, width, height);
273
- gl.disable(gl.SCISSOR_TEST);
274
- gl.clearColor(...this._clearColor);
275
- gl.clear(gl.COLOR_BUFFER_BIT);
276
- }
277
-
278
227
  /**
279
228
  * Creates textures for color schemes and discrete/discretizing ranges.
280
229
  * N.B. Discrete range textures need domain. Thus, this cannot be called
@@ -525,3 +474,55 @@ export function createOrUpdateTexture(gl, options, src, texture) {
525
474
  }
526
475
  return texture;
527
476
  }
477
+
478
+ /**
479
+ *
480
+ * @param {WebGL2RenderingContext} gl
481
+ * @param {import("twgl.js").FramebufferInfo} framebufferInfo
482
+ * @param {number} x
483
+ * @param {number} y
484
+ */
485
+ export function readPickingPixel(gl, framebufferInfo, x, y) {
486
+ const { height, framebuffer } = framebufferInfo;
487
+
488
+ const pixel = new Uint8Array(4);
489
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
490
+ gl.readPixels(x, height - y - 1, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
491
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
492
+
493
+ return pixel;
494
+ }
495
+
496
+ /**
497
+ *
498
+ * @param {WebGL2RenderingContext} gl
499
+ * @param {import("twgl.js").FramebufferInfo} framebufferInfo
500
+ * @param {string} [type]
501
+ */
502
+ export function framebufferToDataUrl(gl, framebufferInfo, type = "image/png") {
503
+ const { width, height } = framebufferInfo;
504
+ const pixels = new Uint8Array(width * height * 4);
505
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebufferInfo.framebuffer);
506
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
507
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
508
+
509
+ const exportCanvas = document.createElement("canvas");
510
+ exportCanvas.width = width;
511
+ exportCanvas.height = height;
512
+ const ctx = exportCanvas.getContext("2d");
513
+ const imageData = ctx.createImageData(width, height);
514
+
515
+ // Flip Y axis (WebGL's origin is bottom-left, canvas is top-left)
516
+ for (let y = 0; y < height; y++) {
517
+ const srcStart = (height - 1 - y) * width * 4;
518
+ const destStart = y * width * 4;
519
+ imageData.data.set(
520
+ pixels.subarray(srcStart, srcStart + width * 4),
521
+ destStart
522
+ );
523
+ }
524
+
525
+ ctx.putImageData(imageData, 0, 0);
526
+
527
+ return exportCanvas.toDataURL(type);
528
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.js"],"names":[],"mappings":";AAwGA;;;;;GAKG;AACH,8BAFW,MAAM,gBAuBhB;sBAhIqB,gBAAgB;qBAFjB,KAAK;iBAGT,kBAAkB;oBACf,6BAA6B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.js"],"names":[],"mappings":";AAyGA;;;;;GAKG;AACH,8BAFW,MAAM,gBAuBhB;sBAjIqB,gBAAgB;qBAFjB,KAAK;iBAGT,kBAAkB;oBACf,6BAA6B"}
package/dist/src/index.js CHANGED
@@ -88,6 +88,7 @@ export async function embed(el, spec, options = {}) {
88
88
  },
89
89
 
90
90
  updateNamedData: genomeSpy.updateNamedData.bind(genomeSpy),
91
+ exportCanvas: genomeSpy.exportCanvas.bind(genomeSpy),
91
92
  };
92
93
  }
93
94
 
@@ -1,2 +1,2 @@
1
- const shader = "in vec4 vColor;in float vSize;in float vNormalLengthInPixels;in float vGamma;out lowp vec4 fragColor;void main(void){float dpr=uDevicePixelRatio;float distance=abs(vNormalLengthInPixels);float opacity=clamp(((vSize/2.0-distance)*dpr),0.0,1.0);opacity=pow(opacity,vGamma);fragColor=vColor*opacity;if(uPickingEnabled){fragColor=vPickingColor;}}";
1
+ const shader = "flat in vec4 vColor;flat in float vSize;in float vNormalLengthInPixels;flat in float vGamma;out lowp vec4 fragColor;void main(void){float dpr=uDevicePixelRatio;float distance=abs(vNormalLengthInPixels);float opacity=clamp(((vSize/2.0-distance)*dpr),0.0,1.0);opacity=pow(opacity,vGamma);fragColor=vColor*opacity;if(uPickingEnabled){fragColor=vPickingColor;}}";
2
2
  export default shader;