@genome-spy/core 0.15.0 → 0.18.1
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/LICENSE +7 -7
- package/README.md +16 -0
- package/dist/index.js +42 -42
- package/dist/{genome-spy-schema.json → schema.json} +1824 -652
- package/dist/style.css +1 -1
- package/package.json +9 -7
- package/src/data/sources/dataUtils.js +50 -3
- package/src/data/sources/dynamicCallbackSource.js +2 -1
- package/src/data/sources/dynamicSource.js +2 -1
- package/src/data/sources/inlineSource.js +3 -5
- package/src/data/sources/namedSource.js +3 -7
- package/src/data/sources/urlSource.js +1 -1
- package/src/data/transforms/aggregate.js +1 -0
- package/src/data/transforms/flattenDelimited.js +6 -0
- package/src/data/transforms/regexFold.js +1 -1
- package/src/data/transforms/stack.js +3 -3
- package/src/embedApi.d.ts +59 -0
- package/src/encoder/accessor.js +6 -6
- package/src/encoder/encoder.js +47 -22
- package/src/genome/scaleIndex.d.ts +38 -0
- package/src/genome/scaleIndex.js +18 -52
- package/src/genome/scaleLocus.d.ts +11 -0
- package/src/genome/scaleLocus.js +12 -16
- package/src/genomeSpy.js +1 -1
- package/src/gl/dataToVertices.js +14 -6
- package/src/gl/includes/fp64-utils.js +10 -0
- package/src/gl/includes/scales.glsl +2 -0
- package/src/gl/webGLHelper.js +3 -1
- package/src/index.js +6 -28
- package/src/marks/link.js +1 -12
- package/src/marks/mark.js +27 -5
- package/src/marks/markUtils.js +41 -25
- package/src/marks/pointMark.js +5 -2
- package/src/marks/rule.js +11 -2
- package/src/scale/glslScaleGenerator.js +16 -29
- package/src/scale/scale.js +10 -0
- package/src/scale/ticks.js +11 -6
- package/src/spec/channel.d.ts +343 -43
- package/src/spec/data.d.ts +14 -3
- package/src/spec/root.d.ts +3 -8
- package/src/spec/scale.d.ts +18 -1
- package/src/spec/view.d.ts +12 -6
- package/src/tooltip/refseqGeneTooltipHandler.js +1 -0
- package/src/types/filetypes.d.ts +10 -0
- package/src/types/internmap.d.ts +22 -0
- package/src/types/vega-loader.d.ts +1 -0
- package/src/utils/addBaseUrl.js +19 -0
- package/src/utils/addBaseUrl.test.js +21 -0
- package/src/utils/arrayUtils.js +12 -6
- package/src/utils/cloner.js +5 -3
- package/src/utils/concatIterables.js +2 -2
- package/src/utils/domainArray.js +0 -8
- package/src/utils/propertyCoalescer.js +9 -4
- package/src/view/axisResolution.js +11 -6
- package/src/view/axisView.js +8 -5
- package/src/view/decoratorView.js +6 -3
- package/src/view/facetView.js +3 -0
- package/src/view/flowBuilder.js +2 -1
- package/src/view/renderingContext/svgViewRenderingContext.js +7 -3
- package/src/view/scaleResolution.js +52 -32
- package/src/view/testUtils.js +7 -4
- package/src/view/unitView.js +15 -9
- package/src/view/view.js +10 -8
- package/src/view/viewFactory.js +2 -0
- package/src/view/viewUtils.js +4 -5
- package/src/options.d.ts +0 -15
- package/src/utils/fisheye.js +0 -60
- package/src/utils/html.js +0 -23
- package/src/utils/html.test.js +0 -13
- package/src/view/channel.js +0 -5
|
@@ -23,8 +23,8 @@ import {
|
|
|
23
23
|
isColorChannel,
|
|
24
24
|
isDiscreteChannel,
|
|
25
25
|
isPositionalChannel,
|
|
26
|
+
isPrimaryPositionalChannel,
|
|
26
27
|
isSecondaryChannel,
|
|
27
|
-
primaryPositionalChannels,
|
|
28
28
|
} from "../encoder/encoder";
|
|
29
29
|
import {
|
|
30
30
|
isChromosomalLocus,
|
|
@@ -34,6 +34,7 @@ import { NominalDomain } from "../utils/domainArray";
|
|
|
34
34
|
import { easeQuadInOut } from "d3-ease";
|
|
35
35
|
import { interpolateZoom } from "d3-interpolate";
|
|
36
36
|
import { shallowArrayEquals } from "../utils/arrayUtils";
|
|
37
|
+
import { isScaleLocus } from "../genome/scaleLocus";
|
|
37
38
|
|
|
38
39
|
export const QUANTITATIVE = "quantitative";
|
|
39
40
|
export const ORDINAL = "ordinal";
|
|
@@ -41,6 +42,15 @@ export const NOMINAL = "nominal";
|
|
|
41
42
|
export const LOCUS = "locus"; // Humdum, should this be "genomic"?
|
|
42
43
|
export const INDEX = "index";
|
|
43
44
|
|
|
45
|
+
/**
|
|
46
|
+
* @template {Channel}[T=Channel]
|
|
47
|
+
* @typedef {{view: import("./unitView").default, channel: T}} ResolutionMember
|
|
48
|
+
* @typedef {import("./unitView").default} UnitView
|
|
49
|
+
* @typedef {import("../encoder/encoder").VegaScale} VegaScale
|
|
50
|
+
* @typedef {import("../utils/domainArray").DomainArray} DomainArray
|
|
51
|
+
* @typedef {import("../genome/genome").ChromosomalLocus} ChromosomalLocus
|
|
52
|
+
*
|
|
53
|
+
*/
|
|
44
54
|
/**
|
|
45
55
|
* Resolution takes care of merging domains and scales from multiple views.
|
|
46
56
|
* This class also provides some utility methods for zooming the scales etc..
|
|
@@ -50,12 +60,6 @@ export const INDEX = "index";
|
|
|
50
60
|
* @typedef {import("./scaleResolutionApi").ScaleResolutionApi} ScaleResolutionApi
|
|
51
61
|
* @implements {ScaleResolutionApi}
|
|
52
62
|
*
|
|
53
|
-
* @typedef {{view: import("./unitView").default, channel: Channel}} ResolutionMember
|
|
54
|
-
* @typedef {import("./unitView").default} UnitView
|
|
55
|
-
* @typedef {import("../encoder/encoder").VegaScale} VegaScale
|
|
56
|
-
* @typedef {import("../utils/domainArray").DomainArray} DomainArray
|
|
57
|
-
* @typedef {import("../genome/genome").ChromosomalLocus} ChromosomalLocus
|
|
58
|
-
*
|
|
59
63
|
* @typedef {import("../spec/channel").Channel} Channel
|
|
60
64
|
* @typedef {import("../spec/scale").Scale} Scale
|
|
61
65
|
* @typedef {import("../spec/scale").NumericDomain} NumericDomain
|
|
@@ -323,7 +327,7 @@ export default class ScaleResolution {
|
|
|
323
327
|
const scale = createScale(props);
|
|
324
328
|
this._scale = scale;
|
|
325
329
|
|
|
326
|
-
if (scale
|
|
330
|
+
if (isScaleLocus(scale)) {
|
|
327
331
|
scale.genome(this.getGenome());
|
|
328
332
|
}
|
|
329
333
|
|
|
@@ -366,7 +370,7 @@ export default class ScaleResolution {
|
|
|
366
370
|
}
|
|
367
371
|
|
|
368
372
|
isZoomable() {
|
|
369
|
-
if (!
|
|
373
|
+
if (!isPrimaryPositionalChannel(this.channel)) {
|
|
370
374
|
return false;
|
|
371
375
|
}
|
|
372
376
|
|
|
@@ -400,13 +404,14 @@ export default class ScaleResolution {
|
|
|
400
404
|
const oldDomain = scale.domain();
|
|
401
405
|
let newDomain = [...oldDomain];
|
|
402
406
|
|
|
407
|
+
// @ts-expect-error
|
|
403
408
|
const anchor = scale.invert(scaleAnchor);
|
|
404
409
|
|
|
405
410
|
if (this.getScaleProps().reverse) {
|
|
406
411
|
pan = -pan;
|
|
407
412
|
}
|
|
408
413
|
|
|
409
|
-
// TODO:
|
|
414
|
+
// TODO: symlog
|
|
410
415
|
switch (scale.type) {
|
|
411
416
|
case "linear":
|
|
412
417
|
case "index":
|
|
@@ -419,22 +424,33 @@ export default class ScaleResolution {
|
|
|
419
424
|
newDomain = zoomLog(newDomain, anchor, scaleFactor);
|
|
420
425
|
break;
|
|
421
426
|
case "pow":
|
|
422
|
-
case "sqrt":
|
|
423
|
-
|
|
427
|
+
case "sqrt": {
|
|
428
|
+
const powScale =
|
|
429
|
+
/** @type {import("d3-scale").ScalePower<number, number>} */ (
|
|
430
|
+
scale
|
|
431
|
+
);
|
|
432
|
+
newDomain = panPow(newDomain, pan || 0, powScale.exponent());
|
|
424
433
|
newDomain = zoomPow(
|
|
425
434
|
newDomain,
|
|
426
435
|
anchor,
|
|
427
436
|
scaleFactor,
|
|
428
|
-
|
|
437
|
+
powScale.exponent()
|
|
429
438
|
);
|
|
430
439
|
break;
|
|
440
|
+
}
|
|
431
441
|
default:
|
|
432
|
-
throw new Error(
|
|
442
|
+
throw new Error(
|
|
443
|
+
"Zooming is not implemented for: " + scale.type
|
|
444
|
+
);
|
|
433
445
|
}
|
|
434
446
|
|
|
435
447
|
// TODO: Use the zoomTo method. Move clamping etc there.
|
|
436
448
|
if (this._zoomExtent) {
|
|
437
|
-
newDomain = clampRange(
|
|
449
|
+
newDomain = clampRange(
|
|
450
|
+
newDomain,
|
|
451
|
+
this._zoomExtent[0],
|
|
452
|
+
this._zoomExtent[1]
|
|
453
|
+
);
|
|
438
454
|
}
|
|
439
455
|
|
|
440
456
|
if ([0, 1].some((i) => newDomain[i] != oldDomain[i])) {
|
|
@@ -491,9 +507,8 @@ export default class ScaleResolution {
|
|
|
491
507
|
},
|
|
492
508
|
});
|
|
493
509
|
*/
|
|
494
|
-
const interpolator = interpolateZoom
|
|
495
|
-
|
|
496
|
-
[tc, 0, tw]
|
|
510
|
+
const interpolator = interpolateZoom([fc, 0, fw], [tc, 0, tw]).rho(
|
|
511
|
+
0.7
|
|
497
512
|
);
|
|
498
513
|
await animator.transition({
|
|
499
514
|
duration: (duration / 1000) * interpolator.duration,
|
|
@@ -672,12 +687,13 @@ export default class ScaleResolution {
|
|
|
672
687
|
*
|
|
673
688
|
* @param {Channel} channel
|
|
674
689
|
* @param {string} dataType
|
|
690
|
+
* @returns {import("../spec/scale").ScaleType}
|
|
675
691
|
*/
|
|
676
692
|
function getDefaultScaleType(channel, dataType) {
|
|
677
693
|
// TODO: Band scale, Bin-Quantitative
|
|
678
694
|
|
|
679
|
-
if (
|
|
680
|
-
if (
|
|
695
|
+
if (dataType == INDEX || dataType == LOCUS) {
|
|
696
|
+
if (isPrimaryPositionalChannel(channel)) {
|
|
681
697
|
return dataType;
|
|
682
698
|
} else {
|
|
683
699
|
// TODO: Also explicitly set scales should be validated
|
|
@@ -688,13 +704,11 @@ function getDefaultScaleType(channel, dataType) {
|
|
|
688
704
|
}
|
|
689
705
|
|
|
690
706
|
/**
|
|
691
|
-
* @type {Partial<Record<Channel,
|
|
707
|
+
* @type {Partial<Record<Channel, (import("../spec/scale").ScaleType | undefined)[]>>}
|
|
692
708
|
* Default types: nominal, ordinal, quantitative.
|
|
693
709
|
* undefined = incompatible, "null" = disabled (pass-thru)
|
|
694
710
|
*/
|
|
695
711
|
const defaults = {
|
|
696
|
-
uniqueId: ["null", undefined, undefined],
|
|
697
|
-
facetIndex: ["null", undefined, undefined],
|
|
698
712
|
x: ["band", "band", "linear"],
|
|
699
713
|
y: ["band", "band", "linear"],
|
|
700
714
|
size: [undefined, "point", "linear"],
|
|
@@ -706,16 +720,24 @@ function getDefaultScaleType(channel, dataType) {
|
|
|
706
720
|
stroke: ["ordinal", "ordinal", "linear"],
|
|
707
721
|
strokeWidth: [undefined, undefined, "linear"],
|
|
708
722
|
shape: ["ordinal", "ordinal", undefined],
|
|
709
|
-
sample: ["null", "null", undefined],
|
|
710
|
-
semanticScore: [undefined, undefined, "null"],
|
|
711
|
-
search: ["null", undefined, undefined],
|
|
712
|
-
text: ["null", "null", "null"],
|
|
713
723
|
dx: [undefined, undefined, "null"],
|
|
714
724
|
dy: [undefined, undefined, "null"],
|
|
715
725
|
angle: [undefined, undefined, "linear"],
|
|
716
726
|
};
|
|
717
727
|
|
|
718
|
-
|
|
728
|
+
/** @type {Channel[]} */
|
|
729
|
+
const typelessChannels = [
|
|
730
|
+
"uniqueId",
|
|
731
|
+
"facetIndex",
|
|
732
|
+
"semanticScore",
|
|
733
|
+
"search",
|
|
734
|
+
"text",
|
|
735
|
+
"sample",
|
|
736
|
+
];
|
|
737
|
+
|
|
738
|
+
const type = typelessChannels.includes(channel)
|
|
739
|
+
? "null"
|
|
740
|
+
: defaults[channel]
|
|
719
741
|
? defaults[channel][[NOMINAL, ORDINAL, QUANTITATIVE].indexOf(dataType)]
|
|
720
742
|
: dataType == QUANTITATIVE
|
|
721
743
|
? "linear"
|
|
@@ -739,10 +761,8 @@ function applyLockedProperties(props, channel) {
|
|
|
739
761
|
props.range = [0, 1];
|
|
740
762
|
}
|
|
741
763
|
|
|
742
|
-
if (channel == "opacity") {
|
|
743
|
-
|
|
744
|
-
props.clamp = true;
|
|
745
|
-
}
|
|
764
|
+
if (channel == "opacity" && isContinuous(props.type)) {
|
|
765
|
+
props.clamp = true;
|
|
746
766
|
}
|
|
747
767
|
}
|
|
748
768
|
|
package/src/view/testUtils.js
CHANGED
|
@@ -15,15 +15,15 @@ import { ViewFactory } from "./viewFactory";
|
|
|
15
15
|
/** @type {<V extends View>(spec: ViewSpec, viewClass: { new(...args: any[]): V }, context?: ViewContext) => V} */
|
|
16
16
|
export function create(spec, viewClass, context = undefined) {
|
|
17
17
|
const viewTypeRegistry = new ViewFactory();
|
|
18
|
-
|
|
19
|
-
const c = {
|
|
18
|
+
|
|
19
|
+
const c = /** @type {ViewContext} */ ({
|
|
20
20
|
...(context || {}),
|
|
21
21
|
accessorFactory: new AccessorFactory(),
|
|
22
22
|
|
|
23
23
|
createView: function (spec, parent, defaultName) {
|
|
24
24
|
return viewTypeRegistry.createView(spec, c, parent, defaultName);
|
|
25
25
|
},
|
|
26
|
-
};
|
|
26
|
+
});
|
|
27
27
|
|
|
28
28
|
const view = c.createView(spec, null, "root");
|
|
29
29
|
|
|
@@ -40,7 +40,10 @@ export async function createAndInitialize(
|
|
|
40
40
|
viewClass,
|
|
41
41
|
context = undefined
|
|
42
42
|
) {
|
|
43
|
-
context =
|
|
43
|
+
context = /** @type {ViewContext} */ ({
|
|
44
|
+
...(context || {}),
|
|
45
|
+
dataFlow: new DataFlow(),
|
|
46
|
+
});
|
|
44
47
|
const view = create(spec, viewClass, context);
|
|
45
48
|
resolveScalesAndAxes(view);
|
|
46
49
|
await initializeData(view, context.dataFlow);
|
package/src/view/unitView.js
CHANGED
|
@@ -170,7 +170,18 @@ export default class UnitView extends ContainerView {
|
|
|
170
170
|
: new AxisResolution(targetChannel);
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
// Looks silly, but keeps type checking happy
|
|
174
|
+
if (isPositionalChannel(channel)) {
|
|
175
|
+
view.resolutions[type][targetChannel].pushUnitView(
|
|
176
|
+
this,
|
|
177
|
+
channel
|
|
178
|
+
);
|
|
179
|
+
} else if (type == "scale") {
|
|
180
|
+
view.resolutions[type][targetChannel].pushUnitView(
|
|
181
|
+
this,
|
|
182
|
+
channel
|
|
183
|
+
);
|
|
184
|
+
}
|
|
174
185
|
}
|
|
175
186
|
}
|
|
176
187
|
|
|
@@ -223,16 +234,11 @@ export default class UnitView extends ContainerView {
|
|
|
223
234
|
}
|
|
224
235
|
|
|
225
236
|
const channelDef = this.mark.encoding[channel];
|
|
237
|
+
// TODO: Broken. Fix.
|
|
226
238
|
if (!isChannelDefWithScale(channelDef)) {
|
|
227
239
|
throw new Error("The channel has no scale, cannot get domain!");
|
|
228
240
|
}
|
|
229
241
|
|
|
230
|
-
const type = channelDef.type;
|
|
231
|
-
if (!type) {
|
|
232
|
-
throw new Error(`No data type for channel "${channel}"!`);
|
|
233
|
-
// TODO: Support defaults
|
|
234
|
-
}
|
|
235
|
-
|
|
236
242
|
return channelDef;
|
|
237
243
|
}
|
|
238
244
|
|
|
@@ -252,7 +258,7 @@ export default class UnitView extends ContainerView {
|
|
|
252
258
|
channelDef.resolutionChannel ?? channel
|
|
253
259
|
);
|
|
254
260
|
return createDomain(
|
|
255
|
-
channelDef.type,
|
|
261
|
+
channelDef.type ?? "nominal",
|
|
256
262
|
// Chrom/pos must be linearized first
|
|
257
263
|
scaleResolution.fromComplexInterval(specDomain)
|
|
258
264
|
);
|
|
@@ -274,7 +280,7 @@ export default class UnitView extends ContainerView {
|
|
|
274
280
|
*/
|
|
275
281
|
extractDataDomain(channel) {
|
|
276
282
|
const channelDef = this._validateDomainQuery(channel);
|
|
277
|
-
const type = channelDef.type;
|
|
283
|
+
const type = channelDef.type ?? "nominal"; // TODO: Should check that this is a channel without scale
|
|
278
284
|
|
|
279
285
|
/** @param {Channel} channel */
|
|
280
286
|
const extract = (channel) => {
|
package/src/view/view.js
CHANGED
|
@@ -142,13 +142,10 @@ export default class View {
|
|
|
142
142
|
*/
|
|
143
143
|
getSizeFromSpec() {
|
|
144
144
|
/**
|
|
145
|
-
*
|
|
146
145
|
* @param {"width" | "height"} dimension
|
|
147
146
|
* @return {SizeDef}
|
|
148
147
|
*/
|
|
149
148
|
const handleSize = (dimension) => {
|
|
150
|
-
/** @type {SizeDef} */
|
|
151
|
-
let sizeDef;
|
|
152
149
|
let value = this.spec[dimension];
|
|
153
150
|
|
|
154
151
|
if (isStepSize(value)) {
|
|
@@ -172,22 +169,27 @@ export default class View {
|
|
|
172
169
|
);
|
|
173
170
|
}
|
|
174
171
|
|
|
172
|
+
// TODO: Type guards maybe?
|
|
173
|
+
const _scale =
|
|
174
|
+
/** @type {import("d3-scale").ScaleBand<any> | import("../genome/scaleLocus").ScaleLocus | import("../genome/scaleIndex").ScaleIndex} */ (
|
|
175
|
+
scale
|
|
176
|
+
);
|
|
177
|
+
|
|
175
178
|
steps = bandSpace(
|
|
176
179
|
steps,
|
|
177
|
-
|
|
178
|
-
|
|
180
|
+
_scale.paddingInner(),
|
|
181
|
+
_scale.paddingOuter()
|
|
179
182
|
);
|
|
180
183
|
|
|
181
|
-
|
|
184
|
+
return { px: steps * stepSize, grow: 0 };
|
|
182
185
|
} else {
|
|
183
186
|
throw new Error(
|
|
184
187
|
"Cannot use 'step' size with missing scale!"
|
|
185
188
|
);
|
|
186
189
|
}
|
|
187
190
|
} else {
|
|
188
|
-
|
|
191
|
+
return (value && parseSizeDef(value)) ?? { px: 0, grow: 1 };
|
|
189
192
|
}
|
|
190
|
-
return sizeDef;
|
|
191
193
|
};
|
|
192
194
|
|
|
193
195
|
return this._cache(
|
package/src/view/viewFactory.js
CHANGED
|
@@ -123,8 +123,10 @@ export function isLayerSpec(spec) {
|
|
|
123
123
|
export function isFacetSpec(spec) {
|
|
124
124
|
return (
|
|
125
125
|
"facet" in spec &&
|
|
126
|
+
// @ts-expect-error
|
|
126
127
|
isObject(spec.facet) &&
|
|
127
128
|
"spec" in spec &&
|
|
129
|
+
// @ts-expect-error
|
|
128
130
|
isObject(spec.spec)
|
|
129
131
|
);
|
|
130
132
|
}
|
package/src/view/viewUtils.js
CHANGED
|
@@ -35,10 +35,9 @@ import { rollup } from "d3-array";
|
|
|
35
35
|
* @typedef {import("../spec/view").ImportSpec} ImportSpec
|
|
36
36
|
* @typedef {import("../spec/view").ImportConfig} ImportConfig
|
|
37
37
|
* @typedef {import("../spec/root").RootSpec} RootSpec
|
|
38
|
-
* @typedef {import("../spec/root").RootConfig} RootConfig
|
|
39
38
|
*
|
|
40
|
-
* @typedef {import("../spec/channel").FacetFieldDef} FacetFieldDef
|
|
41
39
|
* @typedef {import("../spec/view").FacetMapping} FacetMapping
|
|
40
|
+
* @typedef {import("../spec/channel").FacetFieldDef} FacetFieldDef
|
|
42
41
|
*/
|
|
43
42
|
|
|
44
43
|
/**
|
|
@@ -253,14 +252,14 @@ export async function initializeData(root, existingFlow) {
|
|
|
253
252
|
* @param {View} view
|
|
254
253
|
*/
|
|
255
254
|
export function findEncodedFields(view) {
|
|
256
|
-
/** @type {{view: UnitView, channel:
|
|
255
|
+
/** @type {{view: UnitView, channel: import("../spec/channel").Channel, field: import("../spec/channel").Field, type: import("../spec/channel").Type}[]} */
|
|
257
256
|
const fieldInfos = [];
|
|
258
257
|
|
|
259
258
|
view.visit((view) => {
|
|
260
259
|
if (view instanceof UnitView) {
|
|
261
260
|
const encoding = view.getEncoding();
|
|
262
261
|
for (const [channel, def] of Object.entries(encoding)) {
|
|
263
|
-
if (isFieldDef(def)) {
|
|
262
|
+
if (isFieldDef(def) && "type" in def) {
|
|
264
263
|
fieldInfos.push({
|
|
265
264
|
view,
|
|
266
265
|
channel,
|
|
@@ -292,7 +291,7 @@ async function loadExternalViewSpec(spec, baseUrl, viewContext) {
|
|
|
292
291
|
const url = spec.import.url;
|
|
293
292
|
|
|
294
293
|
const importedSpec = JSON.parse(
|
|
295
|
-
await loader.load(url).catch((e) => {
|
|
294
|
+
await loader.load(url).catch((/** @type {Error} */ e) => {
|
|
296
295
|
throw new Error(
|
|
297
296
|
`Could not load imported view spec: ${url} \nReason: ${e.message}`
|
|
298
297
|
);
|
package/src/options.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { TooltipHandler } from "./tooltip/tooltipHandler";
|
|
2
|
-
|
|
3
|
-
export interface EmbedOptions {
|
|
4
|
-
/**
|
|
5
|
-
* A function that allows retrieval of named data sources.
|
|
6
|
-
*
|
|
7
|
-
* TODO: Support dynamic updates, i.e., pushing new data.
|
|
8
|
-
*/
|
|
9
|
-
namedDataProvider?: (name: string) => any[];
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Custom tooltip handlers. Use "default" to override the default handler
|
|
13
|
-
*/
|
|
14
|
-
tooltipHandlers?: Record<string, TooltipHandler>;
|
|
15
|
-
}
|
package/src/utils/fisheye.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Based on:
|
|
3
|
-
* fisheye d3-plugin (c) Mike Bostock
|
|
4
|
-
* https://github.com/d3/d3-plugins/blob/master/fisheye/
|
|
5
|
-
*/
|
|
6
|
-
export default function () {
|
|
7
|
-
let radius = 200;
|
|
8
|
-
let distortion = 2;
|
|
9
|
-
let k0 = 1,
|
|
10
|
-
k1 = 1;
|
|
11
|
-
let focus = 0;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
* @param {number} x
|
|
16
|
-
*/
|
|
17
|
-
function fisheye(x) {
|
|
18
|
-
const dx = x - focus;
|
|
19
|
-
const dd = Math.abs(dx);
|
|
20
|
-
if (!dd || dd >= radius) return x;
|
|
21
|
-
const k = ((k0 * (1 - Math.exp(-dd * k1))) / dd) * 0.75 + 0.25;
|
|
22
|
-
return focus + dx * k;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function rescale() {
|
|
26
|
-
k0 = Math.exp(distortion);
|
|
27
|
-
k0 = (k0 / (k0 - 1)) * radius;
|
|
28
|
-
k1 = distortion / radius;
|
|
29
|
-
return fisheye;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @param {number} _
|
|
34
|
-
*/
|
|
35
|
-
fisheye.radius = function (_) {
|
|
36
|
-
if (!arguments.length) return radius;
|
|
37
|
-
radius = +_;
|
|
38
|
-
return rescale();
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* @param {number} _
|
|
43
|
-
*/
|
|
44
|
-
fisheye.distortion = function (_) {
|
|
45
|
-
if (!arguments.length) return distortion;
|
|
46
|
-
distortion = +_;
|
|
47
|
-
return rescale();
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* @param {number} _
|
|
52
|
-
*/
|
|
53
|
-
fisheye.focus = function (_) {
|
|
54
|
-
if (!arguments.length) return focus;
|
|
55
|
-
focus = _;
|
|
56
|
-
return fisheye;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
return rescale();
|
|
60
|
-
}
|
package/src/utils/html.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/* https://stackoverflow.com/a/41699140/1547896 */
|
|
2
|
-
|
|
3
|
-
export function escapeHtml(str) {
|
|
4
|
-
var map = {
|
|
5
|
-
"&": "&",
|
|
6
|
-
"<": "<",
|
|
7
|
-
">": ">",
|
|
8
|
-
'"': """,
|
|
9
|
-
"'": "'",
|
|
10
|
-
};
|
|
11
|
-
return str.replace(/[&<>"']/g, (m) => map[m]);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function decodeHtml(str) {
|
|
15
|
-
var map = {
|
|
16
|
-
"&": "&",
|
|
17
|
-
"<": "<",
|
|
18
|
-
">": ">",
|
|
19
|
-
""": '"',
|
|
20
|
-
"'": "'",
|
|
21
|
-
};
|
|
22
|
-
return str.replace(/&|<|>|"|'/g, (m) => map[m]);
|
|
23
|
-
}
|
package/src/utils/html.test.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import * as html from "./html";
|
|
2
|
-
|
|
3
|
-
test("Escape HTML", () => {
|
|
4
|
-
expect(html.escapeHtml('< "x" & "y" >')).toEqual(
|
|
5
|
-
"< "x" & "y" >"
|
|
6
|
-
);
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
test("Decode HTML", () => {
|
|
10
|
-
expect(
|
|
11
|
-
html.decodeHtml("< "x" & "y" >")
|
|
12
|
-
).toEqual('< "x" & "y" >');
|
|
13
|
-
});
|