@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.
- package/dist/index.js +224 -0
- package/dist/style.css +1 -0
- package/package.json +54 -0
- package/src/data/collector.js +178 -0
- package/src/data/collector.test.js +82 -0
- package/src/data/dataFlow.js +109 -0
- package/src/data/dataFlow.test.js +3 -0
- package/src/data/facetNode.js +17 -0
- package/src/data/flow.test.js +71 -0
- package/src/data/flowBatch.d.ts +40 -0
- package/src/data/flowNode.js +283 -0
- package/src/data/flowNode.test.js +49 -0
- package/src/data/flowOptimizer.js +117 -0
- package/src/data/flowOptimizer.test.js +192 -0
- package/src/data/flowTestUtils.js +63 -0
- package/src/data/formats/fasta.js +32 -0
- package/src/data/formats/fasta.test.js +26 -0
- package/src/data/sources/dataSource.js +22 -0
- package/src/data/sources/dataSourceFactory.js +24 -0
- package/src/data/sources/dataUtils.js +31 -0
- package/src/data/sources/dynamicCallbackSource.js +56 -0
- package/src/data/sources/dynamicSource.js +36 -0
- package/src/data/sources/inlineSource.js +69 -0
- package/src/data/sources/inlineSource.test.js +55 -0
- package/src/data/sources/namedSource.js +74 -0
- package/src/data/sources/sequenceSource.js +46 -0
- package/src/data/sources/sequenceSource.test.js +45 -0
- package/src/data/sources/urlSource.js +74 -0
- package/src/data/transforms/aggregate.js +69 -0
- package/src/data/transforms/clone.js +40 -0
- package/src/data/transforms/clone.test.js +10 -0
- package/src/data/transforms/coverage.js +187 -0
- package/src/data/transforms/coverage.test.js +122 -0
- package/src/data/transforms/filter.js +37 -0
- package/src/data/transforms/filter.test.js +17 -0
- package/src/data/transforms/filterScoredLabels.js +134 -0
- package/src/data/transforms/flattenCompressedExons.js +57 -0
- package/src/data/transforms/flattenDelimited.js +68 -0
- package/src/data/transforms/flattenDelimited.test.js +86 -0
- package/src/data/transforms/flattenSequence.js +39 -0
- package/src/data/transforms/flattenSequence.test.js +33 -0
- package/src/data/transforms/formula.js +39 -0
- package/src/data/transforms/formula.test.js +18 -0
- package/src/data/transforms/identifier.js +108 -0
- package/src/data/transforms/identifier.test.js +82 -0
- package/src/data/transforms/linearizeGenomicCoordinate.js +101 -0
- package/src/data/transforms/measureText.js +44 -0
- package/src/data/transforms/pileup.js +128 -0
- package/src/data/transforms/pileup.test.js +69 -0
- package/src/data/transforms/project.js +41 -0
- package/src/data/transforms/project.test.js +31 -0
- package/src/data/transforms/regexExtract.js +61 -0
- package/src/data/transforms/regexExtract.test.js +66 -0
- package/src/data/transforms/regexFold.js +141 -0
- package/src/data/transforms/regexFold.test.js +159 -0
- package/src/data/transforms/sample.js +101 -0
- package/src/data/transforms/sample.test.js +37 -0
- package/src/data/transforms/stack.js +137 -0
- package/src/data/transforms/stack.test.js +90 -0
- package/src/data/transforms/transformFactory.js +60 -0
- package/src/encoder/accessor.js +82 -0
- package/src/encoder/accessor.test.js +46 -0
- package/src/encoder/encoder.js +369 -0
- package/src/encoder/encoder.test.js +97 -0
- package/src/fonts/Lato-Regular.json +1267 -0
- package/src/fonts/Lato-Regular.png +0 -0
- package/src/fonts/OFL.txt +93 -0
- package/src/fonts/README.md +3 -0
- package/src/fonts/bmFont.d.ts +58 -0
- package/src/fonts/bmFontManager.js +357 -0
- package/src/fonts/bmFontMetrics.js +108 -0
- package/src/genome/genome.js +305 -0
- package/src/genome/genome.test.js +152 -0
- package/src/genome/genomeStore.js +54 -0
- package/src/genome/locusFormat.js +31 -0
- package/src/genome/scaleIndex.js +199 -0
- package/src/genome/scaleIndex.test.js +61 -0
- package/src/genome/scaleLocus.js +112 -0
- package/src/genome/scaleLocus.test.js +3 -0
- package/src/genomeSpy.js +753 -0
- package/src/gl/arrayBuilder.js +199 -0
- package/src/gl/dataToVertices.js +621 -0
- package/src/gl/includes/common.glsl +63 -0
- package/src/gl/includes/fp64-arithmetic.glsl +187 -0
- package/src/gl/includes/fp64-utils.js +132 -0
- package/src/gl/includes/picking.fragment.glsl +3 -0
- package/src/gl/includes/picking.vertex.glsl +29 -0
- package/src/gl/includes/sampleFacet.glsl +107 -0
- package/src/gl/includes/scales.glsl +79 -0
- package/src/gl/includes/scales_fp64.glsl +30 -0
- package/src/gl/link.fragment.glsl +18 -0
- package/src/gl/link.vertex.glsl +111 -0
- package/src/gl/point.fragment.glsl +123 -0
- package/src/gl/point.vertex.glsl +128 -0
- package/src/gl/rect.fragment.glsl +51 -0
- package/src/gl/rect.vertex.glsl +114 -0
- package/src/gl/rule.fragment.glsl +52 -0
- package/src/gl/rule.vertex.glsl +89 -0
- package/src/gl/text.fragment.glsl +31 -0
- package/src/gl/text.vertex.glsl +246 -0
- package/src/gl/webGLHelper.js +490 -0
- package/src/img/bowtie.svg +1 -0
- package/src/img/genomespy-favicon.svg +34 -0
- package/src/index.html +11 -0
- package/src/index.js +151 -0
- package/src/marks/link.js +189 -0
- package/src/marks/mark.js +867 -0
- package/src/marks/markUtils.js +109 -0
- package/src/marks/pointMark.js +279 -0
- package/src/marks/rectMark.js +236 -0
- package/src/marks/rule.js +231 -0
- package/src/marks/text.js +274 -0
- package/src/options.d.ts +9 -0
- package/src/scale/colorUtils.js +184 -0
- package/src/scale/glslScaleGenerator.js +462 -0
- package/src/scale/scale.js +441 -0
- package/src/scale/scale.test.js +323 -0
- package/src/scale/ticks.js +198 -0
- package/src/scale/ticks.test.js +39 -0
- package/src/singlePageApp.js +13 -0
- package/src/spec/axis.d.ts +296 -0
- package/src/spec/channel.d.ts +127 -0
- package/src/spec/data.d.ts +185 -0
- package/src/spec/font.d.ts +15 -0
- package/src/spec/genome.d.ts +35 -0
- package/src/spec/mark.d.ts +432 -0
- package/src/spec/root.d.ts +22 -0
- package/src/spec/scale.d.ts +265 -0
- package/src/spec/tooltip.d.ts +9 -0
- package/src/spec/transform.d.ts +479 -0
- package/src/spec/view.d.ts +215 -0
- package/src/styles/genome-spy.scss +153 -0
- package/src/tooltip/dataTooltipHandler.js +59 -0
- package/src/tooltip/refseqGeneTooltipHandler.js +77 -0
- package/src/tooltip/tooltipHandler.ts +12 -0
- package/src/types/filetypes.d.ts +4 -0
- package/src/types/flatqueue.d.ts +53 -0
- package/src/types/glsl.d.ts +4 -0
- package/src/types/object.d.ts +21 -0
- package/src/types/vega-scale.d.ts +60 -0
- package/src/utils/animator.js +83 -0
- package/src/utils/arrayUtils.js +55 -0
- package/src/utils/binnedRangeIndex.js +83 -0
- package/src/utils/clamp.js +8 -0
- package/src/utils/cloner.js +32 -0
- package/src/utils/cloner.test.js +23 -0
- package/src/utils/coalesce.js +11 -0
- package/src/utils/coalesce.test.js +15 -0
- package/src/utils/concatIterables.js +26 -0
- package/src/utils/concatIterables.test.js +7 -0
- package/src/utils/debounce.js +37 -0
- package/src/utils/domainArray.js +224 -0
- package/src/utils/domainArray.test.js +129 -0
- package/src/utils/eerp.js +13 -0
- package/src/utils/expression.js +32 -0
- package/src/utils/field.js +28 -0
- package/src/utils/fisheye.js +60 -0
- package/src/utils/formatObject.js +31 -0
- package/src/utils/html.js +23 -0
- package/src/utils/html.test.js +13 -0
- package/src/utils/indexer.js +43 -0
- package/src/utils/indexer.test.js +46 -0
- package/src/utils/inertia.js +124 -0
- package/src/utils/interactionEvent.js +33 -0
- package/src/utils/iterateNestedMaps.js +21 -0
- package/src/utils/iterateNestedMaps.test.js +32 -0
- package/src/utils/kWayMerge.js +42 -0
- package/src/utils/kWayMerge.test.js +25 -0
- package/src/utils/layout/flexLayout.js +336 -0
- package/src/utils/layout/flexLayout.test.js +296 -0
- package/src/utils/layout/padding.js +107 -0
- package/src/utils/layout/point.js +23 -0
- package/src/utils/layout/rectangle.js +282 -0
- package/src/utils/layout/rectangle.test.js +171 -0
- package/src/utils/mergeObjects.js +99 -0
- package/src/utils/mergeObjects.test.js +41 -0
- package/src/utils/numberExtractor.js +24 -0
- package/src/utils/numberExtractor.test.js +5 -0
- package/src/utils/point.js +14 -0
- package/src/utils/propertyCacher.js +70 -0
- package/src/utils/propertyCacher.test.js +84 -0
- package/src/utils/propertyCoalescer.js +37 -0
- package/src/utils/propertyCoalescer.test.js +21 -0
- package/src/utils/reservationMap.js +103 -0
- package/src/utils/reservationMap.test.js +19 -0
- package/src/utils/scaleNull.js +19 -0
- package/src/utils/setOperations.js +75 -0
- package/src/utils/smoothstep.js +10 -0
- package/src/utils/throttle.js +34 -0
- package/src/utils/topK.js +76 -0
- package/src/utils/topK.test.js +63 -0
- package/src/utils/transition.js +74 -0
- package/src/utils/ui/tooltip.js +189 -0
- package/src/utils/url.js +22 -0
- package/src/utils/variableTools.js +24 -0
- package/src/utils/variableTools.test.js +12 -0
- package/src/view/axisResolution.js +135 -0
- package/src/view/axisResolution.test.js +200 -0
- package/src/view/axisView.js +746 -0
- package/src/view/channel.js +5 -0
- package/src/view/concatView.js +296 -0
- package/src/view/containerView.js +141 -0
- package/src/view/decoratorView.js +510 -0
- package/src/view/facetView.js +488 -0
- package/src/view/flowBuilder.js +362 -0
- package/src/view/flowBuilder.test.js +124 -0
- package/src/view/importView.js +19 -0
- package/src/view/layerView.js +60 -0
- package/src/view/rendering.d.ts +44 -0
- package/src/view/renderingContext/compositeViewRenderingContext.js +51 -0
- package/src/view/renderingContext/deferredViewRenderingContext.js +174 -0
- package/src/view/renderingContext/layoutRecorderViewRenderingContext.js +128 -0
- package/src/view/renderingContext/simpleViewRenderingContext.js +62 -0
- package/src/view/renderingContext/svgViewRenderingContext.js +121 -0
- package/src/view/renderingContext/viewRenderingContext.js +41 -0
- package/src/view/scaleResolution.js +756 -0
- package/src/view/scaleResolution.test.js +571 -0
- package/src/view/scaleResolutionApi.d.ts +40 -0
- package/src/view/testUtils.js +48 -0
- package/src/view/unitView.js +368 -0
- package/src/view/view.js +589 -0
- package/src/view/view.test.js +213 -0
- package/src/view/viewContext.d.ts +57 -0
- package/src/view/viewFactory.js +179 -0
- package/src/view/viewFactory.test.js +16 -0
- package/src/view/viewUtils.js +420 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isContinuous,
|
|
3
|
+
isDiscrete,
|
|
4
|
+
isDiscretizing,
|
|
5
|
+
isInterpolating,
|
|
6
|
+
} from "vega-scale";
|
|
7
|
+
import { fp64ify } from "../gl/includes/fp64-utils";
|
|
8
|
+
import { isArray, isBoolean, isNumber, isString } from "vega-util";
|
|
9
|
+
import { color as d3color } from "d3-color";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
getDiscreteRangeMapper,
|
|
13
|
+
isColorChannel,
|
|
14
|
+
isDatumDef,
|
|
15
|
+
isDiscreteChannel,
|
|
16
|
+
getPrimaryChannel,
|
|
17
|
+
} from "../encoder/encoder";
|
|
18
|
+
import { peek } from "../utils/arrayUtils";
|
|
19
|
+
|
|
20
|
+
export const ATTRIBUTE_PREFIX = "attr_";
|
|
21
|
+
export const DOMAIN_PREFIX = "uDomain_";
|
|
22
|
+
export const RANGE_PREFIX = "range_";
|
|
23
|
+
export const SCALE_FUNCTION_PREFIX = "scale_";
|
|
24
|
+
export const SCALED_FUNCTION_PREFIX = "getScaled_";
|
|
25
|
+
export const RANGE_TEXTURE_PREFIX = "uRangeTexture_";
|
|
26
|
+
|
|
27
|
+
// https://stackoverflow.com/a/47543127
|
|
28
|
+
const FLT_MAX = 3.402823466e38;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {import("../spec/channel").Channel} Channel
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Splits a vega-scale type (e.g., linear, sequential-linear) to components.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} type
|
|
38
|
+
*/
|
|
39
|
+
function splitScaleType(type) {
|
|
40
|
+
const match = type.match(/^(?:(\w+)-)?(\w+)$/);
|
|
41
|
+
if (!match) {
|
|
42
|
+
throw new Error("Not a scale type: " + type);
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
family: match[1] || "continuous",
|
|
46
|
+
transform: match[2],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
*
|
|
52
|
+
* @param {Channel} channel
|
|
53
|
+
* @param {number | number[] | string | boolean} value
|
|
54
|
+
*/
|
|
55
|
+
export function generateValueGlsl(channel, value) {
|
|
56
|
+
/** @type {VectorizedValue} */
|
|
57
|
+
let vec;
|
|
58
|
+
if (isDiscreteChannel(channel)) {
|
|
59
|
+
vec = vectorize(getDiscreteRangeMapper(channel)(value));
|
|
60
|
+
} else if (isString(value)) {
|
|
61
|
+
if (isColorChannel(channel)) {
|
|
62
|
+
vec = vectorizeCssColor(value);
|
|
63
|
+
} else {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`String values are not supported on the "${channel}" channel: ${value}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
} else if (isBoolean(value)) {
|
|
69
|
+
vec = vectorize(value ? 1 : 0);
|
|
70
|
+
} else if (value === null) {
|
|
71
|
+
if (isColorChannel(channel)) {
|
|
72
|
+
vec = vectorize([0, 0, 0]);
|
|
73
|
+
} else {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`null value is not supported on the "${channel}" chanel.`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
vec = vectorize(value);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// These could also be passed as uniforms because GPU drivers often handle
|
|
83
|
+
// uniforms as constants and recompile the shader to eliminate dead code etc.
|
|
84
|
+
let glsl = `
|
|
85
|
+
#define ${channel}_DEFINED
|
|
86
|
+
${vec.type} ${SCALED_FUNCTION_PREFIX}${channel}() {
|
|
87
|
+
// Constant value
|
|
88
|
+
return ${vec};
|
|
89
|
+
}`;
|
|
90
|
+
return glsl;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
*
|
|
95
|
+
* @param {Channel} channel
|
|
96
|
+
* @param {any} scale
|
|
97
|
+
* @param {import("../spec/channel").ChannelDef} encoding
|
|
98
|
+
*/
|
|
99
|
+
// eslint-disable-next-line complexity
|
|
100
|
+
export function generateScaleGlsl(channel, scale, encoding) {
|
|
101
|
+
const primary = getPrimaryChannel(channel);
|
|
102
|
+
const attributeName = ATTRIBUTE_PREFIX + channel;
|
|
103
|
+
const domainUniformName = DOMAIN_PREFIX + primary;
|
|
104
|
+
const rangeName = RANGE_PREFIX + primary;
|
|
105
|
+
|
|
106
|
+
const fp64 = !!scale.fp64;
|
|
107
|
+
const attributeType = fp64 ? "vec2" : "float";
|
|
108
|
+
|
|
109
|
+
const domainLength = scale.domain ? scale.domain().length : undefined;
|
|
110
|
+
|
|
111
|
+
/** @type {string} */
|
|
112
|
+
let domainUniform;
|
|
113
|
+
|
|
114
|
+
/** @type {string[]} The generated shader (concatenated at the end) */
|
|
115
|
+
const glsl = [];
|
|
116
|
+
|
|
117
|
+
// For debugging
|
|
118
|
+
glsl.push("");
|
|
119
|
+
glsl.push("/".repeat(70));
|
|
120
|
+
glsl.push(`// Channel: ${channel}`);
|
|
121
|
+
glsl.push("");
|
|
122
|
+
|
|
123
|
+
glsl.push(`#define ${channel}_DEFINED`);
|
|
124
|
+
if (fp64) {
|
|
125
|
+
glsl.push(`#define ${channel}_FP64`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const { transform } = splitScaleType(scale.type);
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @param {string} name
|
|
132
|
+
* @param {...any} args
|
|
133
|
+
*/
|
|
134
|
+
const makeScaleCall = (name, ...args) =>
|
|
135
|
+
// eslint-disable-next-line no-useless-call
|
|
136
|
+
makeFunctionCall.apply(null, [
|
|
137
|
+
name + (fp64 ? "Fp64" : ""),
|
|
138
|
+
"value",
|
|
139
|
+
...args,
|
|
140
|
+
]);
|
|
141
|
+
|
|
142
|
+
let functionCall;
|
|
143
|
+
switch (transform) {
|
|
144
|
+
case "linear":
|
|
145
|
+
functionCall = makeScaleCall("scaleLinear", "domain", rangeName);
|
|
146
|
+
break;
|
|
147
|
+
|
|
148
|
+
case "log":
|
|
149
|
+
functionCall = makeScaleCall(
|
|
150
|
+
"scaleLog",
|
|
151
|
+
"domain",
|
|
152
|
+
rangeName,
|
|
153
|
+
scale.base()
|
|
154
|
+
);
|
|
155
|
+
break;
|
|
156
|
+
|
|
157
|
+
case "symlog":
|
|
158
|
+
functionCall = makeScaleCall(
|
|
159
|
+
"scaleSymlog",
|
|
160
|
+
"domain",
|
|
161
|
+
rangeName,
|
|
162
|
+
scale.constant()
|
|
163
|
+
);
|
|
164
|
+
break;
|
|
165
|
+
|
|
166
|
+
case "pow":
|
|
167
|
+
case "sqrt":
|
|
168
|
+
functionCall = makeScaleCall(
|
|
169
|
+
"scalePow",
|
|
170
|
+
"domain",
|
|
171
|
+
rangeName,
|
|
172
|
+
scale.exponent()
|
|
173
|
+
);
|
|
174
|
+
break;
|
|
175
|
+
|
|
176
|
+
case "index":
|
|
177
|
+
case "locus":
|
|
178
|
+
case "point":
|
|
179
|
+
case "band":
|
|
180
|
+
functionCall = makeScaleCall(
|
|
181
|
+
"scaleBand",
|
|
182
|
+
"domain",
|
|
183
|
+
rangeName,
|
|
184
|
+
scale.paddingInner(),
|
|
185
|
+
scale.paddingOuter(),
|
|
186
|
+
scale.align(),
|
|
187
|
+
encoding.band ?? 0.5
|
|
188
|
+
);
|
|
189
|
+
break;
|
|
190
|
+
|
|
191
|
+
case "ordinal": // Use identity transform and lookup the value from a texture
|
|
192
|
+
case "null":
|
|
193
|
+
case "identity":
|
|
194
|
+
functionCall = makeScaleCall("scaleIdentity");
|
|
195
|
+
break;
|
|
196
|
+
|
|
197
|
+
case "threshold":
|
|
198
|
+
// TODO: Quantile (it's a specialization of threshold scale)
|
|
199
|
+
// TODO: Quantize
|
|
200
|
+
break;
|
|
201
|
+
|
|
202
|
+
default:
|
|
203
|
+
// TODO: Implement log, sqrt, etc...
|
|
204
|
+
throw new Error(
|
|
205
|
+
`Unsupported scale type: ${
|
|
206
|
+
scale.type
|
|
207
|
+
}! ${channel}: ${JSON.stringify(encoding)}`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// N.B. Interpolating scales require unit range
|
|
212
|
+
// TODO: Reverse
|
|
213
|
+
const range =
|
|
214
|
+
isInterpolating(scale.type) ||
|
|
215
|
+
(isContinuous(scale.type) && isColorChannel(channel))
|
|
216
|
+
? [0, 1]
|
|
217
|
+
: scale.range
|
|
218
|
+
? scale.range()
|
|
219
|
+
: undefined;
|
|
220
|
+
|
|
221
|
+
if (range && channel == primary && range.length && range.every(isNumber)) {
|
|
222
|
+
const vectorizedRange = vectorizeRange(range);
|
|
223
|
+
|
|
224
|
+
// Range needs no runtime adjustment (at least for now). Thus, pass it as a constant that the
|
|
225
|
+
// GLSL compiler can optimize away in the case of unit ranges.
|
|
226
|
+
glsl.push(
|
|
227
|
+
`const ${vectorizedRange.type} ${rangeName} = ${vectorizedRange};`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** @type {number} */
|
|
232
|
+
let datum;
|
|
233
|
+
if (isDatumDef(encoding)) {
|
|
234
|
+
if (isNumber(encoding.datum)) {
|
|
235
|
+
datum = encoding.datum;
|
|
236
|
+
} else {
|
|
237
|
+
throw new Error(
|
|
238
|
+
`Only quantitative datums are currently supported in the encoding definition: ${JSON.stringify(
|
|
239
|
+
encoding
|
|
240
|
+
)}`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const returnType = isColorChannel(channel) ? "vec3" : "float";
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* An optional interpolator function that maps the transformed value to the range.
|
|
249
|
+
* @type {string}
|
|
250
|
+
*/
|
|
251
|
+
let interpolate;
|
|
252
|
+
if (isColorChannel(channel)) {
|
|
253
|
+
const textureUniformName = RANGE_TEXTURE_PREFIX + primary;
|
|
254
|
+
if (channel == primary) {
|
|
255
|
+
glsl.push(`uniform sampler2D ${textureUniformName};`);
|
|
256
|
+
}
|
|
257
|
+
if (isContinuous(scale.type)) {
|
|
258
|
+
interpolate = `getInterpolatedColor(${textureUniformName}, transformed)`;
|
|
259
|
+
} else if (isDiscrete(scale.type) || isDiscretizing(scale.type)) {
|
|
260
|
+
interpolate = `getDiscreteColor(${textureUniformName}, int(transformed))`;
|
|
261
|
+
} else {
|
|
262
|
+
throw new Error("Problem with color scale!");
|
|
263
|
+
}
|
|
264
|
+
} else if (scale.type === "ordinal" || isDiscretizing(scale.type)) {
|
|
265
|
+
const textureUniformName = RANGE_TEXTURE_PREFIX + primary;
|
|
266
|
+
if (channel == primary) {
|
|
267
|
+
glsl.push(`uniform sampler2D ${textureUniformName};`);
|
|
268
|
+
}
|
|
269
|
+
interpolate = `getDiscreteColor(${textureUniformName}, int(transformed)).r`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Declare the data: a variable or a constant datum (in domain).
|
|
273
|
+
if (datum !== undefined) {
|
|
274
|
+
// TODO: Datums could also be provided as uniforms, allowing for modifications
|
|
275
|
+
glsl.push(
|
|
276
|
+
`const highp ${attributeType} ${attributeName} = ${vectorize(
|
|
277
|
+
fp64 ? fp64ify(datum) : datum
|
|
278
|
+
)};`
|
|
279
|
+
);
|
|
280
|
+
} else {
|
|
281
|
+
glsl.push(`in highp ${attributeType} ${attributeName};`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** @type {string[]} Channel's scale function*/
|
|
285
|
+
const scaleBody = [];
|
|
286
|
+
|
|
287
|
+
const piecewise = isContinuous(scale.type) && domainLength > 2;
|
|
288
|
+
const needsSlot = isDiscretizing(scale.type) || piecewise;
|
|
289
|
+
|
|
290
|
+
// 1. If scale is piecewise or discretizing, find a matching slot
|
|
291
|
+
scaleBody.push(`int slot = 0;`);
|
|
292
|
+
if (needsSlot) {
|
|
293
|
+
const name = domainUniformName;
|
|
294
|
+
// Use a simple linear search.
|
|
295
|
+
// This cannot be put into a function because GLSL requires fixed array lengths for parameters.
|
|
296
|
+
scaleBody.push(
|
|
297
|
+
piecewise
|
|
298
|
+
? `while (slot < ${name}.length() - 2 && value >= ${name}[slot + 1]) { slot++; }`
|
|
299
|
+
: `while (slot < ${name}.length() && value >= ${name}[slot]) { slot++; }`
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const usesDomain =
|
|
304
|
+
isContinuous(scale.type) ||
|
|
305
|
+
isDiscretizing(scale.type) ||
|
|
306
|
+
["band", "point"].includes(scale.type);
|
|
307
|
+
|
|
308
|
+
// 2. transform
|
|
309
|
+
if (functionCall) {
|
|
310
|
+
const name = domainUniformName;
|
|
311
|
+
if (usesDomain) {
|
|
312
|
+
const dtype = fp64 ? "vec4" : "vec2";
|
|
313
|
+
scaleBody.push(
|
|
314
|
+
`${dtype} domain = ${dtype}(${name}[slot], ${name}[slot + 1]);`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
scaleBody.push(`float transformed = ${functionCall};`);
|
|
319
|
+
|
|
320
|
+
if (piecewise) {
|
|
321
|
+
// TODO: Handle range correctly. Now this assumes unit range.
|
|
322
|
+
scaleBody.push(
|
|
323
|
+
`transformed = (float(slot) + transformed) / (float(${name}.length()) - 1.0);`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
} else {
|
|
327
|
+
// Discretizing scale
|
|
328
|
+
scaleBody.push(`float transformed = float(slot);`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 3. clamp
|
|
332
|
+
if (scale.clamp && scale.clamp()) {
|
|
333
|
+
scaleBody.push(
|
|
334
|
+
`transformed = clampToRange(transformed, ${vectorizeRange(range)});`
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// 4. interpolate or map to a discrete value
|
|
339
|
+
scaleBody.push(`return ${interpolate ?? "transformed"};`);
|
|
340
|
+
|
|
341
|
+
glsl.push(`
|
|
342
|
+
${returnType} ${SCALE_FUNCTION_PREFIX}${channel}(${attributeType} value) {
|
|
343
|
+
${scaleBody.map((x) => ` ${x}\n`).join("")}
|
|
344
|
+
}`);
|
|
345
|
+
|
|
346
|
+
// A convenience getter for the scaled value
|
|
347
|
+
glsl.push(`
|
|
348
|
+
${returnType} ${SCALED_FUNCTION_PREFIX}${channel}() {
|
|
349
|
+
return ${SCALE_FUNCTION_PREFIX}${channel}(${attributeName});
|
|
350
|
+
}`);
|
|
351
|
+
|
|
352
|
+
const concatenated = glsl.join("\n");
|
|
353
|
+
|
|
354
|
+
if (usesDomain && channel == primary) {
|
|
355
|
+
// Band, point, index, and locus scale need the domain extent (the first and last elements).
|
|
356
|
+
const length =
|
|
357
|
+
isContinuous(scale.type) || isDiscretizing(scale.type)
|
|
358
|
+
? domainLength
|
|
359
|
+
: 2;
|
|
360
|
+
domainUniform = `${
|
|
361
|
+
fp64 ? "vec2" : "float"
|
|
362
|
+
} ${domainUniformName}[${length}];`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
glsl: concatenated,
|
|
367
|
+
domainUniform,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Adds a trailing decimal zero so that GLSL is happy.
|
|
373
|
+
*
|
|
374
|
+
* @param {number} number
|
|
375
|
+
* @returns {string}
|
|
376
|
+
*/
|
|
377
|
+
function toDecimal(number) {
|
|
378
|
+
if (!isNumber(number)) {
|
|
379
|
+
throw new Error(`Not a number: ${number}`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (number == Infinity) {
|
|
383
|
+
return "" + FLT_MAX;
|
|
384
|
+
} else if (number == -Infinity) {
|
|
385
|
+
return "" + -FLT_MAX;
|
|
386
|
+
} else {
|
|
387
|
+
let str = `${number}`;
|
|
388
|
+
if (/^(-)?\d+$/.test(str)) {
|
|
389
|
+
str += ".0";
|
|
390
|
+
}
|
|
391
|
+
return str;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Turns a number or number array to float or vec[234] string.
|
|
397
|
+
*
|
|
398
|
+
* @param {number | number[]} value
|
|
399
|
+
* @returns {VectorizedValue}
|
|
400
|
+
*
|
|
401
|
+
* @typedef {string & { type: string, numComponents: number }} VectorizedValue
|
|
402
|
+
*/
|
|
403
|
+
function vectorize(value) {
|
|
404
|
+
if (typeof value == "number") {
|
|
405
|
+
value = [value];
|
|
406
|
+
}
|
|
407
|
+
const numComponents = value.length;
|
|
408
|
+
if (numComponents < 1 || numComponents > 4) {
|
|
409
|
+
throw new Error("Invalid number of components: " + numComponents);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
let type;
|
|
413
|
+
let str;
|
|
414
|
+
|
|
415
|
+
if (numComponents > 1) {
|
|
416
|
+
type = `vec${numComponents}`;
|
|
417
|
+
str = `${type}(${value.map(toDecimal).join(", ")})`;
|
|
418
|
+
} else {
|
|
419
|
+
type = "float";
|
|
420
|
+
str = toDecimal(value[0]);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return Object.assign(str, { type, numComponents });
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* @param {string} color
|
|
428
|
+
*/
|
|
429
|
+
function vectorizeCssColor(color) {
|
|
430
|
+
const rgb = d3color(color).rgb();
|
|
431
|
+
return vectorize([rgb.r, rgb.g, rgb.b].map((x) => x / 255));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
*
|
|
436
|
+
* @param {number[]} range
|
|
437
|
+
*/
|
|
438
|
+
function vectorizeRange(range) {
|
|
439
|
+
return vectorize([range[0], peek(range)]);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
*
|
|
444
|
+
* @param {string} name
|
|
445
|
+
* @param {...any} args
|
|
446
|
+
*/
|
|
447
|
+
function makeFunctionCall(name, ...args) {
|
|
448
|
+
/** @type {string[]} */
|
|
449
|
+
const fixedArgs = [];
|
|
450
|
+
|
|
451
|
+
for (const arg of args) {
|
|
452
|
+
if (isNumber(arg)) {
|
|
453
|
+
fixedArgs.push(toDecimal(arg));
|
|
454
|
+
} else if (isArray(arg)) {
|
|
455
|
+
fixedArgs.push(vectorize(arg));
|
|
456
|
+
} else {
|
|
457
|
+
fixedArgs.push(arg);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return `${name}(${fixedArgs.join(", ")})`;
|
|
462
|
+
}
|