@genome-spy/core 0.14.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 (226) hide show
  1. package/dist/index.js +224 -0
  2. package/dist/style.css +1 -0
  3. package/package.json +54 -0
  4. package/src/data/collector.js +178 -0
  5. package/src/data/collector.test.js +82 -0
  6. package/src/data/dataFlow.js +109 -0
  7. package/src/data/dataFlow.test.js +3 -0
  8. package/src/data/facetNode.js +17 -0
  9. package/src/data/flow.test.js +71 -0
  10. package/src/data/flowBatch.d.ts +40 -0
  11. package/src/data/flowNode.js +283 -0
  12. package/src/data/flowNode.test.js +49 -0
  13. package/src/data/flowOptimizer.js +117 -0
  14. package/src/data/flowOptimizer.test.js +192 -0
  15. package/src/data/flowTestUtils.js +63 -0
  16. package/src/data/formats/fasta.js +32 -0
  17. package/src/data/formats/fasta.test.js +26 -0
  18. package/src/data/sources/dataSource.js +22 -0
  19. package/src/data/sources/dataSourceFactory.js +24 -0
  20. package/src/data/sources/dataUtils.js +31 -0
  21. package/src/data/sources/dynamicCallbackSource.js +56 -0
  22. package/src/data/sources/dynamicSource.js +36 -0
  23. package/src/data/sources/inlineSource.js +69 -0
  24. package/src/data/sources/inlineSource.test.js +55 -0
  25. package/src/data/sources/namedSource.js +74 -0
  26. package/src/data/sources/sequenceSource.js +46 -0
  27. package/src/data/sources/sequenceSource.test.js +45 -0
  28. package/src/data/sources/urlSource.js +74 -0
  29. package/src/data/transforms/aggregate.js +69 -0
  30. package/src/data/transforms/clone.js +40 -0
  31. package/src/data/transforms/clone.test.js +10 -0
  32. package/src/data/transforms/coverage.js +187 -0
  33. package/src/data/transforms/coverage.test.js +122 -0
  34. package/src/data/transforms/filter.js +37 -0
  35. package/src/data/transforms/filter.test.js +17 -0
  36. package/src/data/transforms/filterScoredLabels.js +134 -0
  37. package/src/data/transforms/flattenCompressedExons.js +57 -0
  38. package/src/data/transforms/flattenDelimited.js +68 -0
  39. package/src/data/transforms/flattenDelimited.test.js +86 -0
  40. package/src/data/transforms/flattenSequence.js +39 -0
  41. package/src/data/transforms/flattenSequence.test.js +33 -0
  42. package/src/data/transforms/formula.js +39 -0
  43. package/src/data/transforms/formula.test.js +18 -0
  44. package/src/data/transforms/identifier.js +108 -0
  45. package/src/data/transforms/identifier.test.js +82 -0
  46. package/src/data/transforms/linearizeGenomicCoordinate.js +101 -0
  47. package/src/data/transforms/measureText.js +44 -0
  48. package/src/data/transforms/pileup.js +128 -0
  49. package/src/data/transforms/pileup.test.js +69 -0
  50. package/src/data/transforms/project.js +41 -0
  51. package/src/data/transforms/project.test.js +31 -0
  52. package/src/data/transforms/regexExtract.js +61 -0
  53. package/src/data/transforms/regexExtract.test.js +66 -0
  54. package/src/data/transforms/regexFold.js +141 -0
  55. package/src/data/transforms/regexFold.test.js +159 -0
  56. package/src/data/transforms/sample.js +101 -0
  57. package/src/data/transforms/sample.test.js +37 -0
  58. package/src/data/transforms/stack.js +137 -0
  59. package/src/data/transforms/stack.test.js +90 -0
  60. package/src/data/transforms/transformFactory.js +60 -0
  61. package/src/encoder/accessor.js +82 -0
  62. package/src/encoder/accessor.test.js +46 -0
  63. package/src/encoder/encoder.js +369 -0
  64. package/src/encoder/encoder.test.js +97 -0
  65. package/src/fonts/Lato-Regular.json +1267 -0
  66. package/src/fonts/Lato-Regular.png +0 -0
  67. package/src/fonts/OFL.txt +93 -0
  68. package/src/fonts/README.md +3 -0
  69. package/src/fonts/bmFont.d.ts +58 -0
  70. package/src/fonts/bmFontManager.js +357 -0
  71. package/src/fonts/bmFontMetrics.js +108 -0
  72. package/src/genome/genome.js +305 -0
  73. package/src/genome/genome.test.js +152 -0
  74. package/src/genome/genomeStore.js +54 -0
  75. package/src/genome/locusFormat.js +31 -0
  76. package/src/genome/scaleIndex.js +199 -0
  77. package/src/genome/scaleIndex.test.js +61 -0
  78. package/src/genome/scaleLocus.js +112 -0
  79. package/src/genome/scaleLocus.test.js +3 -0
  80. package/src/genomeSpy.js +753 -0
  81. package/src/gl/arrayBuilder.js +199 -0
  82. package/src/gl/dataToVertices.js +621 -0
  83. package/src/gl/includes/common.glsl +63 -0
  84. package/src/gl/includes/fp64-arithmetic.glsl +187 -0
  85. package/src/gl/includes/fp64-utils.js +132 -0
  86. package/src/gl/includes/picking.fragment.glsl +3 -0
  87. package/src/gl/includes/picking.vertex.glsl +29 -0
  88. package/src/gl/includes/sampleFacet.glsl +107 -0
  89. package/src/gl/includes/scales.glsl +79 -0
  90. package/src/gl/includes/scales_fp64.glsl +30 -0
  91. package/src/gl/link.fragment.glsl +18 -0
  92. package/src/gl/link.vertex.glsl +111 -0
  93. package/src/gl/point.fragment.glsl +123 -0
  94. package/src/gl/point.vertex.glsl +128 -0
  95. package/src/gl/rect.fragment.glsl +51 -0
  96. package/src/gl/rect.vertex.glsl +114 -0
  97. package/src/gl/rule.fragment.glsl +52 -0
  98. package/src/gl/rule.vertex.glsl +89 -0
  99. package/src/gl/text.fragment.glsl +31 -0
  100. package/src/gl/text.vertex.glsl +246 -0
  101. package/src/gl/webGLHelper.js +490 -0
  102. package/src/img/bowtie.svg +1 -0
  103. package/src/img/genomespy-favicon.svg +34 -0
  104. package/src/index.html +11 -0
  105. package/src/index.js +151 -0
  106. package/src/marks/link.js +189 -0
  107. package/src/marks/mark.js +867 -0
  108. package/src/marks/markUtils.js +109 -0
  109. package/src/marks/pointMark.js +279 -0
  110. package/src/marks/rectMark.js +236 -0
  111. package/src/marks/rule.js +231 -0
  112. package/src/marks/text.js +274 -0
  113. package/src/options.d.ts +9 -0
  114. package/src/scale/colorUtils.js +184 -0
  115. package/src/scale/glslScaleGenerator.js +462 -0
  116. package/src/scale/scale.js +441 -0
  117. package/src/scale/scale.test.js +323 -0
  118. package/src/scale/ticks.js +198 -0
  119. package/src/scale/ticks.test.js +39 -0
  120. package/src/singlePageApp.js +13 -0
  121. package/src/spec/axis.d.ts +296 -0
  122. package/src/spec/channel.d.ts +127 -0
  123. package/src/spec/data.d.ts +185 -0
  124. package/src/spec/font.d.ts +15 -0
  125. package/src/spec/genome.d.ts +35 -0
  126. package/src/spec/mark.d.ts +432 -0
  127. package/src/spec/root.d.ts +22 -0
  128. package/src/spec/scale.d.ts +265 -0
  129. package/src/spec/tooltip.d.ts +9 -0
  130. package/src/spec/transform.d.ts +479 -0
  131. package/src/spec/view.d.ts +215 -0
  132. package/src/styles/genome-spy.scss +153 -0
  133. package/src/tooltip/dataTooltipHandler.js +59 -0
  134. package/src/tooltip/refseqGeneTooltipHandler.js +77 -0
  135. package/src/tooltip/tooltipHandler.ts +12 -0
  136. package/src/types/filetypes.d.ts +4 -0
  137. package/src/types/flatqueue.d.ts +53 -0
  138. package/src/types/glsl.d.ts +4 -0
  139. package/src/types/object.d.ts +21 -0
  140. package/src/types/vega-scale.d.ts +60 -0
  141. package/src/utils/animator.js +83 -0
  142. package/src/utils/arrayUtils.js +55 -0
  143. package/src/utils/binnedRangeIndex.js +83 -0
  144. package/src/utils/clamp.js +8 -0
  145. package/src/utils/cloner.js +32 -0
  146. package/src/utils/cloner.test.js +23 -0
  147. package/src/utils/coalesce.js +11 -0
  148. package/src/utils/coalesce.test.js +15 -0
  149. package/src/utils/concatIterables.js +26 -0
  150. package/src/utils/concatIterables.test.js +7 -0
  151. package/src/utils/debounce.js +37 -0
  152. package/src/utils/domainArray.js +224 -0
  153. package/src/utils/domainArray.test.js +129 -0
  154. package/src/utils/eerp.js +13 -0
  155. package/src/utils/expression.js +32 -0
  156. package/src/utils/field.js +28 -0
  157. package/src/utils/fisheye.js +60 -0
  158. package/src/utils/formatObject.js +31 -0
  159. package/src/utils/html.js +23 -0
  160. package/src/utils/html.test.js +13 -0
  161. package/src/utils/indexer.js +43 -0
  162. package/src/utils/indexer.test.js +46 -0
  163. package/src/utils/inertia.js +124 -0
  164. package/src/utils/interactionEvent.js +33 -0
  165. package/src/utils/iterateNestedMaps.js +21 -0
  166. package/src/utils/iterateNestedMaps.test.js +32 -0
  167. package/src/utils/kWayMerge.js +42 -0
  168. package/src/utils/kWayMerge.test.js +25 -0
  169. package/src/utils/layout/flexLayout.js +336 -0
  170. package/src/utils/layout/flexLayout.test.js +296 -0
  171. package/src/utils/layout/padding.js +107 -0
  172. package/src/utils/layout/point.js +23 -0
  173. package/src/utils/layout/rectangle.js +282 -0
  174. package/src/utils/layout/rectangle.test.js +171 -0
  175. package/src/utils/mergeObjects.js +99 -0
  176. package/src/utils/mergeObjects.test.js +41 -0
  177. package/src/utils/numberExtractor.js +24 -0
  178. package/src/utils/numberExtractor.test.js +5 -0
  179. package/src/utils/point.js +14 -0
  180. package/src/utils/propertyCacher.js +70 -0
  181. package/src/utils/propertyCacher.test.js +84 -0
  182. package/src/utils/propertyCoalescer.js +37 -0
  183. package/src/utils/propertyCoalescer.test.js +21 -0
  184. package/src/utils/reservationMap.js +103 -0
  185. package/src/utils/reservationMap.test.js +19 -0
  186. package/src/utils/scaleNull.js +19 -0
  187. package/src/utils/setOperations.js +75 -0
  188. package/src/utils/smoothstep.js +10 -0
  189. package/src/utils/throttle.js +34 -0
  190. package/src/utils/topK.js +76 -0
  191. package/src/utils/topK.test.js +63 -0
  192. package/src/utils/transition.js +74 -0
  193. package/src/utils/ui/tooltip.js +189 -0
  194. package/src/utils/url.js +22 -0
  195. package/src/utils/variableTools.js +24 -0
  196. package/src/utils/variableTools.test.js +12 -0
  197. package/src/view/axisResolution.js +135 -0
  198. package/src/view/axisResolution.test.js +200 -0
  199. package/src/view/axisView.js +746 -0
  200. package/src/view/channel.js +5 -0
  201. package/src/view/concatView.js +296 -0
  202. package/src/view/containerView.js +141 -0
  203. package/src/view/decoratorView.js +510 -0
  204. package/src/view/facetView.js +488 -0
  205. package/src/view/flowBuilder.js +362 -0
  206. package/src/view/flowBuilder.test.js +124 -0
  207. package/src/view/importView.js +19 -0
  208. package/src/view/layerView.js +60 -0
  209. package/src/view/rendering.d.ts +44 -0
  210. package/src/view/renderingContext/compositeViewRenderingContext.js +51 -0
  211. package/src/view/renderingContext/deferredViewRenderingContext.js +174 -0
  212. package/src/view/renderingContext/layoutRecorderViewRenderingContext.js +128 -0
  213. package/src/view/renderingContext/simpleViewRenderingContext.js +62 -0
  214. package/src/view/renderingContext/svgViewRenderingContext.js +121 -0
  215. package/src/view/renderingContext/viewRenderingContext.js +41 -0
  216. package/src/view/scaleResolution.js +756 -0
  217. package/src/view/scaleResolution.test.js +571 -0
  218. package/src/view/scaleResolutionApi.d.ts +40 -0
  219. package/src/view/testUtils.js +48 -0
  220. package/src/view/unitView.js +368 -0
  221. package/src/view/view.js +589 -0
  222. package/src/view/view.test.js +213 -0
  223. package/src/view/viewContext.d.ts +57 -0
  224. package/src/view/viewFactory.js +179 -0
  225. package/src/view/viewFactory.test.js +16 -0
  226. package/src/view/viewUtils.js +420 -0
@@ -0,0 +1,37 @@
1
+ import SampleTransform from "./sample";
2
+ import { extent } from "d3-array";
3
+ import { createChain } from "../../view/flowBuilder";
4
+
5
+ test("SampleTransform produces roughly uniform distributions", () => {
6
+ const size = 10;
7
+ const n = 20;
8
+ const rounds = 10000;
9
+
10
+ const freqs = [];
11
+ for (let i = 0; i < n; i++) {
12
+ freqs[i] = 0;
13
+ }
14
+
15
+ const { dataSource, collector } = createChain(
16
+ new SampleTransform({ type: "sample", size })
17
+ );
18
+
19
+ for (let r = 0; r < rounds; r++) {
20
+ for (let i = 0; i < n; i++) {
21
+ dataSource.handle({ data: i });
22
+ }
23
+ dataSource.complete();
24
+
25
+ for (const datum of collector.getData()) {
26
+ freqs[datum.data] = freqs[datum.data] + 1;
27
+ }
28
+
29
+ dataSource.reset();
30
+ }
31
+
32
+ const e = extent(freqs);
33
+
34
+ // Not a deterministic test! TODO: Come up with some sensical testing method
35
+ expect(e[0]).toBeGreaterThan(4800);
36
+ expect(e[1]).toBeLessThan(5200);
37
+ });
@@ -0,0 +1,137 @@
1
+ import { compare } from "vega-util";
2
+ import { groups as d3groups, sum as d3sum } from "d3-array";
3
+ import FlowNode, { BEHAVIOR_MODIFIES } from "../flowNode";
4
+ import { field } from "../../utils/field";
5
+
6
+ /**
7
+ * @typedef {import("../../spec/transform").StackParams} StackParams
8
+ */
9
+ export default class StackTransform extends FlowNode {
10
+ get behavior() {
11
+ return BEHAVIOR_MODIFIES;
12
+ }
13
+
14
+ /**
15
+ * @param {StackParams} params
16
+ */
17
+ constructor(params) {
18
+ super();
19
+ this.params = params;
20
+
21
+ /** @type {any[]} */
22
+ this.buffer = [];
23
+ }
24
+
25
+ reset() {
26
+ this.buffer = [];
27
+ }
28
+
29
+ /**
30
+ *
31
+ * @param {import("../flowNode").Datum} datum
32
+ */
33
+ handle(datum) {
34
+ this.buffer.push(datum);
35
+ }
36
+
37
+ complete() {
38
+ const params = this.params;
39
+
40
+ const as = params.as || ["y0", "y1"]; // TODO: Validate
41
+
42
+ const comparator = params.sort
43
+ ? compare(params.sort.field, params.sort.order)
44
+ : undefined;
45
+
46
+ const valueAccessor = params.field ? field(params.field) : (d) => 1;
47
+
48
+ const groupFields = params.groupby.map((f) => field(f));
49
+
50
+ const groups = d3groups(this.buffer, (row) =>
51
+ groupFields.map((f) => f(row)).join()
52
+ ).map((a) => a[1]);
53
+
54
+ /** @type {(datum: any) => boolean} */
55
+ let inclusionPredicate = (datum) => true;
56
+
57
+ if (params.baseField) {
58
+ const baseAccessor = field(params.baseField);
59
+ inclusionPredicate = (datum) => baseAccessor(datum) !== null;
60
+ }
61
+
62
+ /** @type {(value: number, sum: number) => number} */
63
+ let offsetF;
64
+
65
+ /** @type {(values: number[], accessor: (datum: any) => number) => number} */
66
+ let sumF;
67
+
68
+ switch (params.offset) {
69
+ case "normalize":
70
+ offsetF = (value, sum) => value / sum;
71
+ sumF = (values, accessor) => d3sum(values, accessor);
72
+ break;
73
+ case "center":
74
+ offsetF = (value, sum) => value - sum / 2;
75
+ sumF = (values, accessor) => d3sum(values, accessor);
76
+ break;
77
+ case "information":
78
+ {
79
+ // Sequence logos: a new way to display consensus sequences (Schneider and Stephens)
80
+ // doi://10.1093/nar/18.20.6097
81
+
82
+ const maxBits = Math.log2(params.cardinality ?? 4);
83
+ offsetF = (value, sum) => value / sum;
84
+ sumF = (values, accessor) => {
85
+ const e = 0; // TODO: Correction factor for small sample sizes
86
+
87
+ const gaps = d3sum(
88
+ values,
89
+ (d) => +!inclusionPredicate(d)
90
+ );
91
+ const total = d3sum(values, accessor);
92
+ const nonGaps = total - gaps;
93
+
94
+ let H = 0;
95
+ for (let i = 0; i < values.length; i++) {
96
+ const datum = values[i];
97
+ if (inclusionPredicate(datum)) {
98
+ const b = accessor(datum) / nonGaps;
99
+ H -= b * Math.log2(b);
100
+ }
101
+ }
102
+
103
+ return (
104
+ (nonGaps / (maxBits - (H + e))) * (nonGaps / total)
105
+ );
106
+ };
107
+ }
108
+ break;
109
+ default:
110
+ offsetF = (value, sum) => value;
111
+ sumF = (values, accessor) => 1;
112
+ }
113
+
114
+ for (const group of groups) {
115
+ if (comparator) {
116
+ group.sort(comparator);
117
+ }
118
+
119
+ const sum = sumF(group, valueAccessor);
120
+
121
+ let prev = 0;
122
+ for (const datum of group) {
123
+ const current = prev + valueAccessor(datum);
124
+
125
+ if (inclusionPredicate(datum)) {
126
+ datum[as[0]] = offsetF(prev, sum);
127
+ datum[as[1]] = offsetF(current, sum);
128
+
129
+ this._propagate(datum);
130
+ prev = current;
131
+ }
132
+ }
133
+ }
134
+
135
+ super.complete();
136
+ }
137
+ }
@@ -0,0 +1,90 @@
1
+ import { processData } from "../flowTestUtils";
2
+ import StackTransform from "./stack";
3
+
4
+ const sampleData = [
5
+ { group: "a", choice: "q", value: 1 },
6
+ { group: "b", choice: "x", value: 1 },
7
+ { group: "b", choice: "y", value: 3 },
8
+ ];
9
+
10
+ /** @type {import("./stack").StackParams} */
11
+ const baseConf = {
12
+ type: "stack",
13
+ field: "value",
14
+ groupby: ["group"],
15
+ sort: {
16
+ field: "value",
17
+ order: "ascending",
18
+ },
19
+ offset: "zero",
20
+ as: ["z0", "z1"],
21
+ };
22
+
23
+ /**
24
+ *
25
+ * @param {import("./stack").StackParams} params
26
+ * @param {any[]} data
27
+ */
28
+ function transform(params, data) {
29
+ return processData(new StackTransform(params), data);
30
+ }
31
+
32
+ describe("Stack transform", () => {
33
+ test("No field", () => {
34
+ const conf = Object.assign({}, baseConf, {
35
+ field: undefined,
36
+ });
37
+
38
+ expect(transform(conf, sampleData)).toEqual([
39
+ { group: "a", choice: "q", value: 1, z0: 0, z1: 1 },
40
+ { group: "b", choice: "x", value: 1, z0: 0, z1: 1 },
41
+ { group: "b", choice: "y", value: 3, z0: 1, z1: 2 },
42
+ ]);
43
+ });
44
+
45
+ test("Zero offset", () => {
46
+ expect(transform(baseConf, sampleData)).toEqual([
47
+ { group: "a", choice: "q", value: 1, z0: 0, z1: 1 },
48
+ { group: "b", choice: "x", value: 1, z0: 0, z1: 1 },
49
+ { group: "b", choice: "y", value: 3, z0: 1, z1: 4 },
50
+ ]);
51
+ });
52
+
53
+ test("Normalize offset", () => {
54
+ const conf = Object.assign({}, baseConf, {
55
+ offset: "normalize",
56
+ });
57
+
58
+ expect(transform(conf, sampleData)).toEqual([
59
+ { group: "a", choice: "q", value: 1, z0: 0, z1: 1 },
60
+ { group: "b", choice: "x", value: 1, z0: 0, z1: 0.25 },
61
+ { group: "b", choice: "y", value: 3, z0: 0.25, z1: 1 },
62
+ ]);
63
+ });
64
+
65
+ test("Center offset", () => {
66
+ const conf = Object.assign({}, baseConf, {
67
+ offset: "center",
68
+ });
69
+
70
+ expect(transform(conf, sampleData)).toEqual([
71
+ { group: "a", choice: "q", value: 1, z0: -0.5, z1: 0.5 },
72
+ { group: "b", choice: "x", value: 1, z0: -2, z1: -1 },
73
+ { group: "b", choice: "y", value: 3, z0: -1, z1: 2 },
74
+ ]);
75
+ });
76
+
77
+ test("Descending sort", () => {
78
+ const conf = Object.assign({}, baseConf, {
79
+ sort: {
80
+ field: "value",
81
+ order: "descending",
82
+ },
83
+ });
84
+ expect(transform(conf, sampleData)).toEqual([
85
+ { group: "a", choice: "q", value: 1, z0: 0, z1: 1 },
86
+ { group: "b", choice: "y", value: 3, z0: 0, z1: 3 },
87
+ { group: "b", choice: "x", value: 1, z0: 3, z1: 4 },
88
+ ]);
89
+ });
90
+ });
@@ -0,0 +1,60 @@
1
+ import Collector from "../collector";
2
+ import CoverageTransform from "./coverage";
3
+ import FilterScoredLabelsTransform from "./filterScoredLabels";
4
+ import FilterTransform from "./filter";
5
+ import FlattenCompressedExonsTransform from "./flattenCompressedExons";
6
+ import FlattenDelimitedTransform from "./flattenDelimited";
7
+ import FormulaTransform from "./formula";
8
+ import LinearizeGenomicCoordinate from "./linearizeGenomicCoordinate";
9
+ import MeasureTextTransform from "./measureText";
10
+ import PileupTransform from "./pileup";
11
+ import ProjectTransform from "./project";
12
+ import RegexExtractTransform from "./regexExtract";
13
+ import RegexFoldTransform from "./regexFold";
14
+ import SampleTransform from "./sample";
15
+ import StackTransform from "./stack";
16
+ import FlattenSequenceTransform from "./flattenSequence";
17
+ import AggregateTransform from "./aggregate";
18
+ import IdentifierTransform from "./identifier";
19
+
20
+ /**
21
+ * TODO: Make this dynamic
22
+ *
23
+ * @typedef {import("../../view/view").default} View
24
+ * @typedef {import("../flowNode").default} FlowNode
25
+ *
26
+ * @type {Record<string, new (params: any, view?: View) => FlowNode>}
27
+ */
28
+ export const transforms = {
29
+ aggregate: AggregateTransform,
30
+ collect: Collector,
31
+ coverage: CoverageTransform,
32
+ filterScoredLabels: FilterScoredLabelsTransform,
33
+ filter: FilterTransform,
34
+ flattenCompressedExons: FlattenCompressedExonsTransform,
35
+ flattenDelimited: FlattenDelimitedTransform,
36
+ flattenSequence: FlattenSequenceTransform,
37
+ formula: FormulaTransform,
38
+ identifier: IdentifierTransform,
39
+ linearizeGenomicCoordinate: LinearizeGenomicCoordinate,
40
+ measureText: MeasureTextTransform,
41
+ pileup: PileupTransform,
42
+ project: ProjectTransform,
43
+ regexExtract: RegexExtractTransform,
44
+ regexFold: RegexFoldTransform,
45
+ sample: SampleTransform,
46
+ stack: StackTransform,
47
+ };
48
+
49
+ /**
50
+ * @param {import("../../spec/transform").TransformParamsBase} params
51
+ * @param {View} [view]
52
+ */
53
+ export default function createTransform(params, view) {
54
+ const Transform = transforms[params.type];
55
+ if (Transform) {
56
+ return new Transform(params, view);
57
+ } else {
58
+ throw new Error("Unknown transform: " + params.type);
59
+ }
60
+ }
@@ -0,0 +1,82 @@
1
+ import createFunction from "../utils/expression";
2
+
3
+ import { accessorFields, constant } from "vega-util";
4
+ import { isDatumDef, isExprDef, isFieldDef } from "./encoder";
5
+ import { field } from "../utils/field";
6
+
7
+ /**
8
+ * @typedef {Object} AccessorMetadata
9
+ * @prop {boolean} constant True if the accessor returns the same value for all objects
10
+ * @prop {string[]} fields The fields that the return value is based on (if any)
11
+ *
12
+ * @typedef {(function(any):any) & AccessorMetadata} Accessor
13
+ *
14
+ * @typedef {import("../view/viewUtils").ChannelDef} ChannelDef
15
+ */
16
+ export default class AccessorFactory {
17
+ constructor() {
18
+ /** @type {(function(ChannelDef):Accessor)[]} */
19
+ this.accessorCreators = [];
20
+
21
+ this.register((channelDef) => {
22
+ if (isFieldDef(channelDef)) {
23
+ try {
24
+ /** @type {Accessor} */
25
+ const accessor = field(channelDef.field);
26
+ accessor.constant = false;
27
+ accessor.fields = accessorFields(accessor);
28
+ return accessor;
29
+ } catch (e) {
30
+ throw new Error(`Invalid field definition: ${e.message}`);
31
+ }
32
+ }
33
+ });
34
+
35
+ this.register((channelDef) =>
36
+ isExprDef(channelDef)
37
+ ? createExpressionAccessor(channelDef.expr)
38
+ : undefined
39
+ );
40
+
41
+ this.register((channelDef) => {
42
+ if (isDatumDef(channelDef)) {
43
+ /** @type {Accessor} */
44
+ const accessor = constant(channelDef.datum);
45
+ accessor.constant = true; // Can be optimized downstream
46
+ accessor.fields = [];
47
+ return accessor;
48
+ }
49
+ });
50
+ }
51
+
52
+ /**
53
+ *
54
+ * @param {function(ChannelDef):Accessor} creator
55
+ */
56
+ register(creator) {
57
+ this.accessorCreators.push(creator);
58
+ }
59
+
60
+ /**
61
+ *
62
+ * @param {ChannelDef} encoding
63
+ */
64
+ createAccessor(encoding) {
65
+ for (const creator of this.accessorCreators) {
66
+ const accessor = creator(encoding);
67
+ if (accessor) {
68
+ return accessor;
69
+ }
70
+ }
71
+ }
72
+ }
73
+
74
+ /**
75
+ * @param {string} expr
76
+ */
77
+ function createExpressionAccessor(expr) {
78
+ /** @type {Accessor} */
79
+ const accessor = createFunction(expr);
80
+ accessor.constant = accessor.fields.length == 0; // Not bulletproof, eh
81
+ return accessor;
82
+ }
@@ -0,0 +1,46 @@
1
+ import AccessorFactory from "./accessor";
2
+
3
+ const af = new AccessorFactory();
4
+
5
+ const datum = {
6
+ a: 1,
7
+ b: 2,
8
+ "x.c": 3,
9
+ };
10
+
11
+ test("Creates a field accessor", () => {
12
+ const a = af.createAccessor({ field: "a" });
13
+ expect(a(datum)).toEqual(1);
14
+ expect(a.constant).toBeFalsy();
15
+ expect(a.fields).toEqual(["a"]);
16
+ });
17
+
18
+ test("Creates an expression accessor", () => {
19
+ const a = af.createAccessor({ expr: `datum.b + datum['x\.c']` });
20
+ expect(a(datum)).toEqual(5);
21
+ expect(a.constant).toBeFalsy();
22
+ expect(a.fields.sort()).toEqual(["b", "x.c"].sort());
23
+ });
24
+
25
+ test("Creates a constant accessor", () => {
26
+ const a = af.createAccessor({ datum: 0 });
27
+ expect(a(datum)).toEqual(0);
28
+ expect(a.constant).toBeTruthy();
29
+ expect(a.fields).toEqual([]);
30
+ });
31
+
32
+ test("Returns undefined on incomplete encoding spec", () => {
33
+ expect(af.createAccessor({})).toBeUndefined();
34
+ });
35
+
36
+ test("Registers and creates a custom accessor", () => {
37
+ const af = new AccessorFactory();
38
+ af.register((encoding) => {
39
+ if (encoding.iddqd && encoding.idkfa) {
40
+ return (datum) =>
41
+ `${datum[encoding.iddqd]}-${datum[encoding.idkfa]}`;
42
+ }
43
+ });
44
+
45
+ expect(af.createAccessor({ iddqd: "a", idkfa: "b" })(datum)).toEqual("1-2");
46
+ });