@genome-spy/core 0.64.0 → 0.66.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/dist/bundle/{index-CCJIjehY.js → AbortablePromiseCache-CcuMrnn7.js} +22 -91
- package/dist/bundle/browser-BRemItdO.js +138 -0
- package/dist/bundle/index-BatuyGAI.js +271 -0
- package/dist/bundle/index-ByuE8dvu.js +332 -0
- package/dist/bundle/index-Cq3QFUxX.js +1781 -0
- package/dist/bundle/{index-C08YCM2T.js → index-D-w7Mmt9.js} +246 -126
- package/dist/bundle/index-D28m8tSW.js +1607 -0
- package/dist/bundle/index-D74H8TTz.js +508 -0
- package/dist/bundle/index-DbJ0oeYM.js +631 -0
- package/dist/bundle/index.es.js +15034 -13842
- package/dist/bundle/index.js +223 -237
- package/dist/bundle/inflate-GtwLkvSP.js +1048 -0
- package/dist/bundle/unzip-NywezaRR.js +1492 -0
- package/dist/schema.json +22 -4
- package/dist/src/config/scaleDefaults.d.ts +8 -0
- package/dist/src/config/scaleDefaults.d.ts.map +1 -0
- package/dist/src/config/scaleDefaults.js +45 -0
- package/dist/src/data/collector.d.ts +7 -2
- package/dist/src/data/collector.d.ts.map +1 -1
- package/dist/src/data/collector.js +13 -2
- package/dist/src/data/dataFlow.d.ts +20 -42
- package/dist/src/data/dataFlow.d.ts.map +1 -1
- package/dist/src/data/dataFlow.js +57 -80
- package/dist/src/data/flowHandle.d.ts +15 -0
- package/dist/src/data/flowHandle.d.ts.map +1 -0
- package/dist/src/data/flowHandle.js +13 -0
- package/dist/src/data/flowInit.d.ts +85 -0
- package/dist/src/data/flowInit.d.ts.map +1 -0
- package/dist/src/data/flowInit.js +238 -0
- package/dist/src/data/flowInit.test.d.ts +2 -0
- package/dist/src/data/flowInit.test.d.ts.map +1 -0
- package/dist/src/data/flowOptimizer.d.ts +6 -4
- package/dist/src/data/flowOptimizer.d.ts.map +1 -1
- package/dist/src/data/flowOptimizer.js +29 -14
- package/dist/src/data/sources/lazy/axisTickSource.js +1 -1
- package/dist/src/data/sources/lazy/bamSource.js +1 -1
- package/dist/src/data/sources/lazy/bigBedSource.js +1 -1
- package/dist/src/data/sources/lazy/bigWigSource.js +1 -1
- package/dist/src/data/sources/lazy/gff3Source.d.ts +2 -6
- package/dist/src/data/sources/lazy/gff3Source.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/gff3Source.js +4 -8
- package/dist/src/data/sources/lazy/indexedFastaSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/indexedFastaSource.js +17 -17
- package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts +1 -1
- package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/singleAxisLazySource.js +10 -3
- package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +5 -1
- package/dist/src/data/sources/lazy/tabixSource.js +1 -1
- package/dist/src/data/transforms/filterScoredLabels.d.ts +1 -1
- package/dist/src/data/transforms/filterScoredLabels.d.ts.map +1 -1
- package/dist/src/data/transforms/filterScoredLabels.js +1 -1
- package/dist/src/data/transforms/linearizeGenomicCoordinate.d.ts.map +1 -1
- package/dist/src/data/transforms/linearizeGenomicCoordinate.js +2 -1
- package/dist/src/encoder/encoder.d.ts +1 -1
- package/dist/src/encoder/encoder.d.ts.map +1 -1
- package/dist/src/encoder/encoder.js +1 -1
- package/dist/src/genome/scaleLocus.d.ts +39 -0
- package/dist/src/genome/scaleLocus.d.ts.map +1 -1
- package/dist/src/genome/scaleLocus.js +76 -0
- package/dist/src/genomeSpy/canvasExport.d.ts +19 -0
- package/dist/src/genomeSpy/canvasExport.d.ts.map +1 -0
- package/dist/src/genomeSpy/canvasExport.js +66 -0
- package/dist/src/genomeSpy/containerUi.d.ts +17 -0
- package/dist/src/genomeSpy/containerUi.d.ts.map +1 -0
- package/dist/src/genomeSpy/containerUi.js +78 -0
- package/dist/src/genomeSpy/eventListenerRegistry.d.ts +19 -0
- package/dist/src/genomeSpy/eventListenerRegistry.d.ts.map +1 -0
- package/dist/src/genomeSpy/eventListenerRegistry.js +38 -0
- package/dist/src/genomeSpy/inputBindingManager.d.ts +14 -0
- package/dist/src/genomeSpy/inputBindingManager.d.ts.map +1 -0
- package/dist/src/genomeSpy/inputBindingManager.js +63 -0
- package/dist/src/genomeSpy/interactionController.d.ts +40 -0
- package/dist/src/genomeSpy/interactionController.d.ts.map +1 -0
- package/dist/src/genomeSpy/interactionController.js +371 -0
- package/dist/src/genomeSpy/keyboardListenerManager.d.ts +10 -0
- package/dist/src/genomeSpy/keyboardListenerManager.d.ts.map +1 -0
- package/dist/src/genomeSpy/keyboardListenerManager.js +31 -0
- package/dist/src/genomeSpy/loadingIndicatorManager.d.ts +15 -0
- package/dist/src/genomeSpy/loadingIndicatorManager.d.ts.map +1 -0
- package/dist/src/genomeSpy/loadingIndicatorManager.js +92 -0
- package/dist/src/genomeSpy/renderCoordinator.d.ts +22 -0
- package/dist/src/genomeSpy/renderCoordinator.d.ts.map +1 -0
- package/dist/src/genomeSpy/renderCoordinator.js +118 -0
- package/dist/src/genomeSpy/viewContextFactory.d.ts +18 -0
- package/dist/src/genomeSpy/viewContextFactory.d.ts.map +1 -0
- package/dist/src/genomeSpy/viewContextFactory.js +79 -0
- package/dist/src/genomeSpy/viewDataInit.d.ts +12 -0
- package/dist/src/genomeSpy/viewDataInit.d.ts.map +1 -0
- package/dist/src/genomeSpy/viewDataInit.js +41 -0
- package/dist/src/genomeSpy/viewHierarchyConfig.d.ts +14 -0
- package/dist/src/genomeSpy/viewHierarchyConfig.d.ts.map +1 -0
- package/dist/src/genomeSpy/viewHierarchyConfig.js +24 -0
- package/dist/src/genomeSpy/viewHighlight.d.ts +5 -0
- package/dist/src/genomeSpy/viewHighlight.d.ts.map +1 -0
- package/dist/src/genomeSpy/viewHighlight.js +30 -0
- package/dist/src/genomeSpy.d.ts +17 -72
- package/dist/src/genomeSpy.d.ts.map +1 -1
- package/dist/src/genomeSpy.js +180 -789
- package/dist/src/gl/glslScaleGenerator.d.ts +1 -1
- package/dist/src/gl/webGLHelper.d.ts +2 -2
- package/dist/src/gl/webGLHelper.d.ts.map +1 -1
- package/dist/src/gl/webGLHelper.js +4 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -12
- package/dist/src/marks/mark.d.ts +1 -0
- package/dist/src/marks/mark.d.ts.map +1 -1
- package/dist/src/marks/mark.js +26 -3
- package/dist/src/{view → scales}/axisResolution.d.ts +12 -14
- package/dist/src/scales/axisResolution.d.ts.map +1 -0
- package/dist/src/{view → scales}/axisResolution.js +38 -12
- package/dist/src/scales/axisResolution.test.d.ts.map +1 -0
- package/dist/src/scales/scaleDomainAggregator.d.ts +57 -0
- package/dist/src/scales/scaleDomainAggregator.d.ts.map +1 -0
- package/dist/src/scales/scaleDomainAggregator.js +162 -0
- package/dist/src/scales/scaleDomainAggregator.test.d.ts +2 -0
- package/dist/src/scales/scaleDomainAggregator.test.d.ts.map +1 -0
- package/dist/src/scales/scaleInstanceManager.d.ts +40 -0
- package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -0
- package/dist/src/scales/scaleInstanceManager.js +313 -0
- package/dist/src/scales/scaleInstanceManager.test.d.ts +2 -0
- package/dist/src/scales/scaleInstanceManager.test.d.ts.map +1 -0
- package/dist/src/scales/scaleInteractionController.d.ts +73 -0
- package/dist/src/scales/scaleInteractionController.d.ts.map +1 -0
- package/dist/src/scales/scaleInteractionController.js +336 -0
- package/dist/src/scales/scaleInteractionController.test.d.ts +2 -0
- package/dist/src/scales/scaleInteractionController.test.d.ts.map +1 -0
- package/dist/src/scales/scalePropsResolver.d.ts +23 -0
- package/dist/src/scales/scalePropsResolver.d.ts.map +1 -0
- package/dist/src/scales/scalePropsResolver.js +74 -0
- package/dist/src/{view → scales}/scaleResolution.d.ts +53 -31
- package/dist/src/scales/scaleResolution.d.ts.map +1 -0
- package/dist/src/scales/scaleResolution.js +658 -0
- package/dist/src/scales/scaleResolution.test.d.ts.map +1 -0
- package/dist/src/scales/scaleResolutionConstants.d.ts +6 -0
- package/dist/src/scales/scaleResolutionConstants.d.ts.map +1 -0
- package/dist/src/scales/scaleResolutionConstants.js +5 -0
- package/dist/src/scales/scaleRules.d.ts +16 -0
- package/dist/src/scales/scaleRules.d.ts.map +1 -0
- package/dist/src/scales/scaleRules.js +103 -0
- package/dist/src/scales/scaleRules.test.d.ts +2 -0
- package/dist/src/scales/scaleRules.test.d.ts.map +1 -0
- package/dist/src/spec/channel.d.ts +13 -18
- package/dist/src/spec/sampleView.d.ts +3 -2
- package/dist/src/spec/scale.d.ts +6 -0
- package/dist/src/types/embedApi.d.ts +5 -0
- package/dist/src/types/scaleResolutionApi.d.ts +1 -1
- package/dist/src/types/viewContext.d.ts +1 -1
- package/dist/src/view/concatView.d.ts +18 -0
- package/dist/src/view/concatView.d.ts.map +1 -1
- package/dist/src/view/concatView.js +73 -0
- package/dist/src/view/concatView.test.d.ts +2 -0
- package/dist/src/view/concatView.test.d.ts.map +1 -0
- package/dist/src/view/containerMutationHelper.d.ts +74 -0
- package/dist/src/view/containerMutationHelper.d.ts.map +1 -0
- package/dist/src/view/containerMutationHelper.js +114 -0
- package/dist/src/view/containerView.d.ts +0 -7
- package/dist/src/view/containerView.d.ts.map +1 -1
- package/dist/src/view/containerView.js +0 -10
- package/dist/src/view/facetView.d.ts.map +1 -1
- package/dist/src/view/facetView.js +0 -14
- package/dist/src/view/flowBuilder.d.ts +2 -2
- package/dist/src/view/flowBuilder.d.ts.map +1 -1
- package/dist/src/view/flowBuilder.js +21 -4
- package/dist/src/view/gridView/gridChild.d.ts +11 -0
- package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
- package/dist/src/view/gridView/gridChild.js +32 -6
- package/dist/src/view/gridView/gridView.d.ts +39 -1
- package/dist/src/view/gridView/gridView.d.ts.map +1 -1
- package/dist/src/view/gridView/gridView.js +113 -42
- package/dist/src/view/gridView/gridView.test.d.ts +2 -0
- package/dist/src/view/gridView/gridView.test.d.ts.map +1 -0
- package/dist/src/view/gridView/scrollbar.d.ts +39 -8
- package/dist/src/view/gridView/scrollbar.d.ts.map +1 -1
- package/dist/src/view/gridView/scrollbar.js +184 -69
- package/dist/src/view/gridView/selectionRect.d.ts +8 -4
- package/dist/src/view/gridView/selectionRect.d.ts.map +1 -1
- package/dist/src/view/gridView/selectionRect.js +28 -3
- package/dist/src/view/gridView/selectionRect.test.d.ts +2 -0
- package/dist/src/view/gridView/selectionRect.test.d.ts.map +1 -0
- package/dist/src/view/layerView.d.ts +14 -0
- package/dist/src/view/layerView.d.ts.map +1 -1
- package/dist/src/view/layerView.js +66 -0
- package/dist/src/view/layerView.test.d.ts +2 -0
- package/dist/src/view/layerView.test.d.ts.map +1 -0
- package/dist/src/view/paramMediator.d.ts +2 -1
- package/dist/src/view/paramMediator.d.ts.map +1 -1
- package/dist/src/view/paramMediator.js +13 -1
- package/dist/src/view/testUtils.d.ts.map +1 -1
- package/dist/src/view/testUtils.js +18 -5
- package/dist/src/view/unitView.d.ts.map +1 -1
- package/dist/src/view/unitView.js +52 -12
- package/dist/src/view/view.d.ts +23 -7
- package/dist/src/view/view.d.ts.map +1 -1
- package/dist/src/view/view.js +61 -5
- package/dist/src/view/viewDispose.test.d.ts +2 -0
- package/dist/src/view/viewDispose.test.d.ts.map +1 -0
- package/dist/src/view/viewUtils.d.ts +4 -4
- package/dist/src/view/viewUtils.d.ts.map +1 -1
- package/dist/src/view/viewUtils.js +19 -15
- package/dist/src/view/viewUtils.test.d.ts +2 -0
- package/dist/src/view/viewUtils.test.d.ts.map +1 -0
- package/package.json +10 -10
- package/dist/bundle/__vite-browser-external-C--ziKoh.js +0 -8
- package/dist/bundle/_commonjsHelpers-DjF3Plf2.js +0 -26
- package/dist/bundle/index-5ajWdKly.js +0 -1319
- package/dist/bundle/index-B03-Om4z.js +0 -274
- package/dist/bundle/index-BftNdA0O.js +0 -27
- package/dist/bundle/index-Bg7C4Xat.js +0 -2750
- package/dist/bundle/index-C3QR8Lv6.js +0 -2131
- package/dist/bundle/index-DTcHjAHp.js +0 -505
- package/dist/bundle/index-DnIkxb0L.js +0 -1025
- package/dist/bundle/index-Ww3TAo6_.js +0 -71
- package/dist/bundle/index-g8iXgW0W.js +0 -651
- package/dist/bundle/long-B-FASCSo.js +0 -2387
- package/dist/bundle/remoteFile-BuaqFGWk.js +0 -94
- package/dist/src/data/collector.test.js +0 -138
- package/dist/src/data/dataFlow.test.js +0 -5
- package/dist/src/data/flow.test.js +0 -81
- package/dist/src/data/flowNode.test.js +0 -50
- package/dist/src/data/flowOptimizer.test.js +0 -204
- package/dist/src/data/formats/fasta.test.js +0 -27
- package/dist/src/data/sources/inlineSource.test.js +0 -63
- package/dist/src/data/sources/sequenceSource.test.js +0 -81
- package/dist/src/data/transforms/aggregate.test.js +0 -134
- package/dist/src/data/transforms/clone.test.js +0 -11
- package/dist/src/data/transforms/coverage.test.js +0 -238
- package/dist/src/data/transforms/filter.test.js +0 -20
- package/dist/src/data/transforms/flatten.test.js +0 -96
- package/dist/src/data/transforms/flattenDelimited.test.js +0 -90
- package/dist/src/data/transforms/flattenSequence.test.js +0 -34
- package/dist/src/data/transforms/formula.test.js +0 -25
- package/dist/src/data/transforms/identifier.test.js +0 -92
- package/dist/src/data/transforms/pileup.test.js +0 -70
- package/dist/src/data/transforms/project.test.js +0 -32
- package/dist/src/data/transforms/regexExtract.test.js +0 -70
- package/dist/src/data/transforms/regexFold.test.js +0 -201
- package/dist/src/data/transforms/sample.test.js +0 -38
- package/dist/src/data/transforms/stack.test.js +0 -91
- package/dist/src/encoder/accessor.test.js +0 -162
- package/dist/src/encoder/encoder.test.js +0 -105
- package/dist/src/genome/genome.test.js +0 -268
- package/dist/src/genome/genomes.test.js +0 -8
- package/dist/src/genome/scaleIndex.test.js +0 -78
- package/dist/src/genome/scaleLocus.test.js +0 -4
- package/dist/src/scale/scale.test.js +0 -326
- package/dist/src/scale/ticks.test.js +0 -46
- package/dist/src/selection/selection.test.js +0 -14
- package/dist/src/utils/addBaseUrl.test.js +0 -30
- package/dist/src/utils/binnedIndex.test.js +0 -201
- package/dist/src/utils/cloner.test.js +0 -35
- package/dist/src/utils/coalesce.test.js +0 -16
- package/dist/src/utils/concatIterables.test.js +0 -8
- package/dist/src/utils/domainArray.test.js +0 -130
- package/dist/src/utils/indexer.test.js +0 -49
- package/dist/src/utils/interactionEvent.test.js +0 -35
- package/dist/src/utils/iterateNestedMaps.test.js +0 -33
- package/dist/src/utils/kWayMerge.test.js +0 -30
- package/dist/src/utils/mergeObjects.test.js +0 -42
- package/dist/src/utils/numberExtractor.test.js +0 -6
- package/dist/src/utils/propertyCacher.test.js +0 -89
- package/dist/src/utils/propertyCoalescer.test.js +0 -25
- package/dist/src/utils/radixSort.test.js +0 -51
- package/dist/src/utils/reservationMap.test.js +0 -20
- package/dist/src/utils/ringBuffer.test.js +0 -39
- package/dist/src/utils/topK.test.js +0 -54
- package/dist/src/utils/trees.test.js +0 -135
- package/dist/src/utils/url.test.js +0 -28
- package/dist/src/utils/variableTools.test.js +0 -13
- package/dist/src/view/axisResolution.d.ts.map +0 -1
- package/dist/src/view/axisResolution.test.d.ts.map +0 -1
- package/dist/src/view/axisResolution.test.js +0 -206
- package/dist/src/view/flowBuilder.test.js +0 -125
- package/dist/src/view/layout/flexLayout.test.js +0 -323
- package/dist/src/view/layout/grid.test.js +0 -71
- package/dist/src/view/layout/rectangle.test.js +0 -192
- package/dist/src/view/paramMediator.test.js +0 -260
- package/dist/src/view/scaleResolution.d.ts.map +0 -1
- package/dist/src/view/scaleResolution.js +0 -1049
- package/dist/src/view/scaleResolution.test.d.ts.map +0 -1
- package/dist/src/view/scaleResolution.test.js +0 -645
- package/dist/src/view/view.test.js +0 -245
- package/dist/src/view/viewFactory.test.js +0 -25
- /package/dist/src/{view → scales}/axisResolution.test.d.ts +0 -0
- /package/dist/src/{view → scales}/scaleResolution.test.d.ts +0 -0
|
@@ -1,1049 +0,0 @@
|
|
|
1
|
-
import scaleLocus, { isScaleLocus } from "../genome/scaleLocus.js";
|
|
2
|
-
import scaleIndex from "../genome/scaleIndex.js";
|
|
3
|
-
import scaleNull from "../utils/scaleNull.js";
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
panLinear,
|
|
7
|
-
zoomLinear,
|
|
8
|
-
clampRange,
|
|
9
|
-
span,
|
|
10
|
-
panLog,
|
|
11
|
-
zoomLog,
|
|
12
|
-
panPow,
|
|
13
|
-
zoomPow,
|
|
14
|
-
isArray,
|
|
15
|
-
isObject,
|
|
16
|
-
isBoolean,
|
|
17
|
-
} from "vega-util";
|
|
18
|
-
import { scale as vegaScale, isDiscrete, isContinuous } from "vega-scale";
|
|
19
|
-
|
|
20
|
-
import mergeObjects from "../utils/mergeObjects.js";
|
|
21
|
-
import createScale, { configureScale } from "../scale/scale.js";
|
|
22
|
-
|
|
23
|
-
import {
|
|
24
|
-
isColorChannel,
|
|
25
|
-
isDiscreteChannel,
|
|
26
|
-
isPositionalChannel,
|
|
27
|
-
isPrimaryPositionalChannel,
|
|
28
|
-
isSecondaryChannel,
|
|
29
|
-
} from "../encoder/encoder.js";
|
|
30
|
-
import {
|
|
31
|
-
isChromosomalLocus,
|
|
32
|
-
isChromosomalLocusInterval,
|
|
33
|
-
} from "../genome/genome.js";
|
|
34
|
-
import createDomain, { NominalDomain } from "../utils/domainArray.js";
|
|
35
|
-
import { easeCubicInOut } from "d3-ease";
|
|
36
|
-
import { asArray, shallowArrayEquals } from "../utils/arrayUtils.js";
|
|
37
|
-
import eerp from "../utils/eerp.js";
|
|
38
|
-
import { isExprRef } from "./paramMediator.js";
|
|
39
|
-
|
|
40
|
-
// Register scaleLocus to Vega-Scale.
|
|
41
|
-
// Loci are discrete but the scale's domain can be adjusted in a continuous manner.
|
|
42
|
-
vegaScale("index", scaleIndex, ["continuous"]);
|
|
43
|
-
vegaScale("locus", scaleLocus, ["continuous"]);
|
|
44
|
-
vegaScale("null", scaleNull, []);
|
|
45
|
-
|
|
46
|
-
export const QUANTITATIVE = "quantitative";
|
|
47
|
-
export const ORDINAL = "ordinal";
|
|
48
|
-
export const NOMINAL = "nominal";
|
|
49
|
-
export const LOCUS = "locus";
|
|
50
|
-
export const INDEX = "index";
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* @template {ChannelWithScale}[T=ChannelWithScale]
|
|
54
|
-
*
|
|
55
|
-
* @typedef {object} ScaleResolutionMember
|
|
56
|
-
* @prop {import("./unitView.js").default} view TODO: Get rid of the view reference
|
|
57
|
-
* @prop {T} channel
|
|
58
|
-
* @prop {import("../spec/channel.js").ChannelDefWithScale} channelDef
|
|
59
|
-
* @prop {(channel: ChannelWithScale, type: import("../spec/channel.js").Type) => DomainArray} dataDomainSource
|
|
60
|
-
*/
|
|
61
|
-
/**
|
|
62
|
-
* Resolution takes care of merging domains and scales from multiple views.
|
|
63
|
-
* This class also provides some utility methods for zooming the scales etc..
|
|
64
|
-
*
|
|
65
|
-
* TODO: This has grown a bit too fat. Consider splitting.
|
|
66
|
-
*
|
|
67
|
-
* @implements {ScaleResolutionApi}
|
|
68
|
-
*/
|
|
69
|
-
export default class ScaleResolution {
|
|
70
|
-
/**
|
|
71
|
-
* @typedef {import("../types/scaleResolutionApi.js").ScaleResolutionApi} ScaleResolutionApi
|
|
72
|
-
* @typedef {import("../types/scaleResolutionApi.js").ScaleResolutionEventType} ScaleResolutionEventType
|
|
73
|
-
* @typedef {import("../spec/channel.js").Channel} Channel
|
|
74
|
-
* @typedef {import("../spec/channel.js").ChannelWithScale} ChannelWithScale
|
|
75
|
-
* @typedef {import("../spec/scale.js").NumericDomain} NumericDomain
|
|
76
|
-
* @typedef {import("../spec/scale.js").ScalarDomain} ScalarDomain
|
|
77
|
-
* @typedef {import("../spec/scale.js").ComplexDomain} ComplexDomain
|
|
78
|
-
* @typedef {import("../spec/scale.js").ZoomParams} ZoomParams
|
|
79
|
-
* @typedef {import("./unitView.js").default} UnitView
|
|
80
|
-
* @typedef {import("../types/encoder.js").VegaScale} VegaScale
|
|
81
|
-
* @typedef {import("../utils/domainArray.js").DomainArray} DomainArray
|
|
82
|
-
* @typedef {import("../genome/genome.js").ChromosomalLocus} ChromosomalLocus
|
|
83
|
-
* @typedef {import("../types/scaleResolutionApi.js").ScaleResolutionListener} ScaleResolutionListener
|
|
84
|
-
*
|
|
85
|
-
* @typedef {VegaScale & { props: import("../spec/scale.js").Scale }} ScaleWithProps
|
|
86
|
-
*/
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* @type {Record<ScaleResolutionEventType, Set<ScaleResolutionListener>>}
|
|
90
|
-
*/
|
|
91
|
-
#listeners = {
|
|
92
|
-
domain: new Set(),
|
|
93
|
-
range: new Set(),
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
/** @type {ScaleWithProps} */
|
|
97
|
-
#scale;
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* The initial domain before any zooming.
|
|
101
|
-
* @type {any[]}
|
|
102
|
-
*/
|
|
103
|
-
#initialDomain;
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Keeps track of the expression references in the range. If range is modified,
|
|
107
|
-
* new expressions are created and the old ones must be invalidated.
|
|
108
|
-
*
|
|
109
|
-
* @type {Set<import("./paramMediator.js").ExprRefFunction>}
|
|
110
|
-
*/
|
|
111
|
-
#rangeExprRefListeners = new Set();
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* @param {Channel} channel
|
|
115
|
-
*/
|
|
116
|
-
constructor(channel) {
|
|
117
|
-
this.channel = channel;
|
|
118
|
-
/** @type {ScaleResolutionMember[]} The involved views */
|
|
119
|
-
this.members = [];
|
|
120
|
-
/** @type {import("../spec/channel.js").Type} Data type (quantitative, nominal, etc...) */
|
|
121
|
-
this.type = null;
|
|
122
|
-
|
|
123
|
-
/** @type {string} An optional unique identifier for the scale */
|
|
124
|
-
this.name = undefined;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
get #firstMemberView() {
|
|
128
|
-
return this.members[0].view;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
get #viewContext() {
|
|
132
|
-
return this.#firstMemberView.context;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
get zoomExtent() {
|
|
136
|
-
return (
|
|
137
|
-
(this.#scale &&
|
|
138
|
-
isContinuous(this.#scale.type) &&
|
|
139
|
-
this.#getZoomExtent()) ?? [-Infinity, Infinity]
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Adds a listener that is called when the scale domain is changed,
|
|
145
|
-
* e.g., zoomed. The call is synchronous and happens before the views
|
|
146
|
-
* are rendered.
|
|
147
|
-
*
|
|
148
|
-
* @param {ScaleResolutionEventType} type
|
|
149
|
-
* @param {ScaleResolutionListener} listener function
|
|
150
|
-
*/
|
|
151
|
-
addEventListener(type, listener) {
|
|
152
|
-
this.#listeners[type].add(listener);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* @param {ScaleResolutionEventType} type
|
|
157
|
-
* @param {ScaleResolutionListener} listener function
|
|
158
|
-
*/
|
|
159
|
-
removeEventListener(type, listener) {
|
|
160
|
-
this.#listeners[type].delete(listener);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* @param {ScaleResolutionEventType} type
|
|
165
|
-
*/
|
|
166
|
-
#notifyListeners(type) {
|
|
167
|
-
for (const listener of this.#listeners[type].values()) {
|
|
168
|
-
listener({
|
|
169
|
-
type,
|
|
170
|
-
scaleResolution: this,
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Add a view to this resolution.
|
|
177
|
-
* N.B. This is expected to be called in depth-first order
|
|
178
|
-
*
|
|
179
|
-
* @param {ScaleResolutionMember} newMember
|
|
180
|
-
*/
|
|
181
|
-
addMember(newMember) {
|
|
182
|
-
const { channel, channelDef } = newMember;
|
|
183
|
-
|
|
184
|
-
// A convenience hack for cases where the new member should adapt
|
|
185
|
-
// the scale type to the existing one. For example: SelectionRect
|
|
186
|
-
// TODO: Add test
|
|
187
|
-
const adapt = channelDef.type == null && this.type;
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
// @ts-expect-error "sample" is not really a channel with scale
|
|
191
|
-
channel != "sample" &&
|
|
192
|
-
!channelDef.type &&
|
|
193
|
-
!isSecondaryChannel(channel) &&
|
|
194
|
-
!adapt
|
|
195
|
-
) {
|
|
196
|
-
throw new Error(
|
|
197
|
-
`The "type" property must be defined in channel definition: "${channel}": ${JSON.stringify(
|
|
198
|
-
channelDef
|
|
199
|
-
)}. Must be one of: "quantitative", "ordinal", "nominal", "locus", "index"`
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// A hack for sample channel, which really doesn't have a scale but the
|
|
204
|
-
// domain is needed when samples are not specified explicitly.
|
|
205
|
-
// @ts-expect-error "sample" is not really a channel with scale
|
|
206
|
-
const type = channel == "sample" ? "nominal" : channelDef.type;
|
|
207
|
-
const name = channelDef?.scale?.name;
|
|
208
|
-
|
|
209
|
-
if (name) {
|
|
210
|
-
if (this.name !== undefined && name != this.name) {
|
|
211
|
-
throw new Error(
|
|
212
|
-
`Shared scales have conflicting names: "${name}" vs. "${this.name}"!`
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
this.name = name;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (!adapt) {
|
|
219
|
-
if (!this.type) {
|
|
220
|
-
this.type = type;
|
|
221
|
-
} else if (type !== this.type && !isSecondaryChannel(channel)) {
|
|
222
|
-
// TODO: Include a reference to the layer
|
|
223
|
-
throw new Error(
|
|
224
|
-
`Can not use shared scale for different data types: ${this.type} vs. ${type}. Use "resolve: independent" for channel ${this.channel}`
|
|
225
|
-
);
|
|
226
|
-
// Actually, point scale could be changed into band scale
|
|
227
|
-
// TODO: Use the same merging logic as in: https://github.com/vega/vega-lite/blob/master/src/scale.ts
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
this.members.push(newMember);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Returns true if the domain has been defined explicitly, i.e. not extracted from the data.
|
|
236
|
-
*/
|
|
237
|
-
#isExplicitDomain() {
|
|
238
|
-
return !!this.#getConfiguredDomain();
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
#isDomainInitialized() {
|
|
242
|
-
const s = this.#scale;
|
|
243
|
-
if (!s) {
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const domain = s.domain();
|
|
248
|
-
|
|
249
|
-
// We could alternatively have a flag that is set when the domain is initialized.
|
|
250
|
-
if (isContinuous(s.type)) {
|
|
251
|
-
return (
|
|
252
|
-
domain.length > 2 ||
|
|
253
|
-
(domain.length == 2 && (domain[0] !== 0 || domain[1] !== 0))
|
|
254
|
-
);
|
|
255
|
-
} else {
|
|
256
|
-
return domain.length > 0;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Collects and merges scale properties from the participating views.
|
|
262
|
-
* Does not include inferred default values such as schemes etc.
|
|
263
|
-
*
|
|
264
|
-
* @returns {import("../spec/scale.js").Scale}
|
|
265
|
-
*/
|
|
266
|
-
#getMergedScaleProps() {
|
|
267
|
-
const propArray = this.members
|
|
268
|
-
.map((member) => member.channelDef.scale)
|
|
269
|
-
.filter((props) => props !== undefined);
|
|
270
|
-
|
|
271
|
-
// TODO: Disabled scale: https://vega.github.io/vega-lite/docs/scale.html#disable
|
|
272
|
-
return mergeObjects(propArray, "scale", ["domain"]);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Returns the merged scale properties supplemented with inferred properties
|
|
277
|
-
* and domain.
|
|
278
|
-
*
|
|
279
|
-
* @param {boolean} [extractDataDomain]
|
|
280
|
-
* @returns {import("../spec/scale.js").Scale}
|
|
281
|
-
*/
|
|
282
|
-
#getScaleProps(extractDataDomain = false) {
|
|
283
|
-
const mergedProps = this.#getMergedScaleProps();
|
|
284
|
-
if (mergedProps === null || mergedProps.type == "null") {
|
|
285
|
-
// No scale (pass-thru)
|
|
286
|
-
// TODO: Check that the channel is compatible
|
|
287
|
-
return { type: "null" };
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const props = {
|
|
291
|
-
...this.#getDefaultScaleProperties(this.type),
|
|
292
|
-
...mergedProps,
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
if (!props.type) {
|
|
296
|
-
props.type = getDefaultScaleType(this.channel, this.type);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const domain = this.#getInitialDomain(extractDataDomain);
|
|
300
|
-
|
|
301
|
-
if (domain && domain.length > 0) {
|
|
302
|
-
props.domain = domain;
|
|
303
|
-
} else if (isDiscrete(props.type)) {
|
|
304
|
-
props.domain = new NominalDomain();
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (!props.domain && props.domainMid !== undefined) {
|
|
308
|
-
// Initialize with a bogus domain so that scale.js can inject the domainMid.
|
|
309
|
-
// The number of domain elements must be know before the glsl scale is generated.
|
|
310
|
-
props.domain = [props.domainMin ?? 0, props.domainMax ?? 1];
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Reverse discrete y axis
|
|
314
|
-
if (
|
|
315
|
-
this.channel == "y" &&
|
|
316
|
-
isDiscrete(props.type) &&
|
|
317
|
-
props.reverse == undefined
|
|
318
|
-
) {
|
|
319
|
-
props.reverse = true;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (props.range && props.scheme) {
|
|
323
|
-
delete props.scheme;
|
|
324
|
-
// TODO: Props should be set more intelligently
|
|
325
|
-
/*
|
|
326
|
-
throw new Error(
|
|
327
|
-
`Scale has both "range" and "scheme" defined! Views: ${this._getViewPaths()}`
|
|
328
|
-
);
|
|
329
|
-
*/
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// By default, index and locus scales are zoomable, others are not
|
|
333
|
-
if (!("zoom" in props) && ["index", "locus"].includes(props.type)) {
|
|
334
|
-
props.zoom = true;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
applyLockedProperties(props, this.channel);
|
|
338
|
-
|
|
339
|
-
return props;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* Configures range. If range is an array of expressions, they are evaluated
|
|
344
|
-
* and the scale is updated when the expressions change.
|
|
345
|
-
*/
|
|
346
|
-
#configureRange() {
|
|
347
|
-
const props = this.#scale.props;
|
|
348
|
-
const range = props.range;
|
|
349
|
-
this.#rangeExprRefListeners.forEach((fn) => fn.invalidate());
|
|
350
|
-
|
|
351
|
-
if (!range || !isArray(range)) {
|
|
352
|
-
// Named ranges?
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* @param {T} array
|
|
358
|
-
* @param {boolean} reverse
|
|
359
|
-
* @returns {T}
|
|
360
|
-
* @template T
|
|
361
|
-
*/
|
|
362
|
-
const flip = (array, reverse) =>
|
|
363
|
-
// @ts-ignore TODO: Fix the type (should be a generic union array type)
|
|
364
|
-
reverse ? array.slice().reverse() : array;
|
|
365
|
-
|
|
366
|
-
if (range.some(isExprRef)) {
|
|
367
|
-
/** @type {(() => void)[]} */
|
|
368
|
-
let expressions;
|
|
369
|
-
|
|
370
|
-
const evaluateAndSet = () => {
|
|
371
|
-
this.#scale.range(
|
|
372
|
-
flip(
|
|
373
|
-
expressions.map((expr) => expr()),
|
|
374
|
-
props.reverse
|
|
375
|
-
)
|
|
376
|
-
);
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
expressions = range.map((elem) => {
|
|
380
|
-
if (isExprRef(elem)) {
|
|
381
|
-
const fn =
|
|
382
|
-
this.#firstMemberView.paramMediator.createExpression(
|
|
383
|
-
elem.expr
|
|
384
|
-
);
|
|
385
|
-
fn.addListener(evaluateAndSet);
|
|
386
|
-
this.#rangeExprRefListeners.add(fn);
|
|
387
|
-
return () => fn(null);
|
|
388
|
-
} else {
|
|
389
|
-
return () => elem;
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
evaluateAndSet();
|
|
394
|
-
} else {
|
|
395
|
-
this.#scale.range(flip(range, props.reverse));
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
*
|
|
401
|
-
* @param {boolean} extractDataDomain
|
|
402
|
-
*/
|
|
403
|
-
#getInitialDomain(extractDataDomain = false) {
|
|
404
|
-
// TODO: intersect the domain with zoom extent (if it's defined)
|
|
405
|
-
return (
|
|
406
|
-
this.#getConfiguredDomain() ??
|
|
407
|
-
(this.type == LOCUS
|
|
408
|
-
? this.getGenome().getExtent()
|
|
409
|
-
: extractDataDomain
|
|
410
|
-
? this.getDataDomain()
|
|
411
|
-
: [])
|
|
412
|
-
);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Unions the configured domains of all participating views.
|
|
417
|
-
*
|
|
418
|
-
* @return { DomainArray }
|
|
419
|
-
*/
|
|
420
|
-
#getConfiguredDomain() {
|
|
421
|
-
const domains = this.members
|
|
422
|
-
.map((member) => member.channelDef)
|
|
423
|
-
.filter((channelDef) => channelDef.scale?.domain)
|
|
424
|
-
.map((channelDef) =>
|
|
425
|
-
// TODO: Handle ExprRefs and Param in domain
|
|
426
|
-
createDomain(
|
|
427
|
-
channelDef.type,
|
|
428
|
-
// Chrom/pos must be linearized first
|
|
429
|
-
this.fromComplexInterval(channelDef.scale.domain)
|
|
430
|
-
)
|
|
431
|
-
);
|
|
432
|
-
|
|
433
|
-
if (domains.length > 0) {
|
|
434
|
-
return domains.reduce((acc, curr) => acc.extendAll(curr));
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Extracts and unions the data domains of all participating views.
|
|
440
|
-
*
|
|
441
|
-
* @return { DomainArray }
|
|
442
|
-
*/
|
|
443
|
-
getDataDomain() {
|
|
444
|
-
return this.members
|
|
445
|
-
.map((member) =>
|
|
446
|
-
member.dataDomainSource?.(member.channel, this.type)
|
|
447
|
-
)
|
|
448
|
-
.filter((domain) => !!domain)
|
|
449
|
-
.reduce((acc, curr) => acc.extendAll(curr));
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* Reconfigures the scale: updates domain and other settings
|
|
454
|
-
*/
|
|
455
|
-
reconfigure() {
|
|
456
|
-
const scale = this.#scale;
|
|
457
|
-
|
|
458
|
-
if (!scale || scale.type == "null") {
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
const domainWasInitialized = this.#isDomainInitialized();
|
|
463
|
-
const previousDomain = scale.domain();
|
|
464
|
-
|
|
465
|
-
const props = this.#getScaleProps(true);
|
|
466
|
-
configureScale({ ...props, range: undefined }, scale);
|
|
467
|
-
|
|
468
|
-
// Annotate the scale with the new props
|
|
469
|
-
scale.props = props;
|
|
470
|
-
this.#configureRange();
|
|
471
|
-
|
|
472
|
-
if (!this.#initialDomain && isContinuous(scale.type)) {
|
|
473
|
-
const domain = scale.domain();
|
|
474
|
-
if (span(domain) > 0) {
|
|
475
|
-
this.#initialDomain = domain;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
if (!domainWasInitialized) {
|
|
480
|
-
this.#initialDomain = scale.domain();
|
|
481
|
-
this.#notifyListeners("domain");
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
const newDomain = scale.domain();
|
|
486
|
-
if (!shallowArrayEquals(newDomain, previousDomain)) {
|
|
487
|
-
if (this.isZoomable()) {
|
|
488
|
-
// Don't mess with zoomed views, restore the previous domain
|
|
489
|
-
scale.domain(previousDomain);
|
|
490
|
-
} else if (this.#isZoomingSupported()) {
|
|
491
|
-
// It can be zoomed, so lets make a smooth transition.
|
|
492
|
-
// Restore the previous domain and zoom smoothly to the new domain.
|
|
493
|
-
scale.domain(previousDomain);
|
|
494
|
-
this.zoomTo(newDomain, 500); // TODO: Configurable duration
|
|
495
|
-
} else {
|
|
496
|
-
// Update immediately if the previous domain was the initial domain [0, 0]
|
|
497
|
-
this.#notifyListeners("domain");
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* @returns {ScaleWithProps}
|
|
504
|
-
*/
|
|
505
|
-
get scale() {
|
|
506
|
-
if (this.#scale) {
|
|
507
|
-
return this.#scale;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
const props = this.#getScaleProps();
|
|
511
|
-
|
|
512
|
-
const scale = createScale({ ...props, range: undefined });
|
|
513
|
-
// Annotate the scale with props
|
|
514
|
-
scale.props = props;
|
|
515
|
-
|
|
516
|
-
if ("unknown" in scale) {
|
|
517
|
-
// Never allow implicit domain construction
|
|
518
|
-
scale.unknown(null);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
this.#scale = scale;
|
|
522
|
-
this.#configureRange();
|
|
523
|
-
|
|
524
|
-
if (isScaleLocus(scale)) {
|
|
525
|
-
scale.genome(this.getGenome());
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// Hijack the range method
|
|
529
|
-
const range = scale.range;
|
|
530
|
-
if (range) {
|
|
531
|
-
const notify = () => this.#notifyListeners("range");
|
|
532
|
-
scale.range = function (/** @type {any} */ _) {
|
|
533
|
-
if (arguments.length) {
|
|
534
|
-
range(_);
|
|
535
|
-
notify();
|
|
536
|
-
} else {
|
|
537
|
-
return range();
|
|
538
|
-
}
|
|
539
|
-
};
|
|
540
|
-
// The initial setting
|
|
541
|
-
notify();
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
return scale;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
getDomain() {
|
|
548
|
-
return this.scale.domain();
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* @returns {NumericDomain | ComplexDomain}
|
|
553
|
-
*/
|
|
554
|
-
getComplexDomain() {
|
|
555
|
-
return (
|
|
556
|
-
this.getGenome()?.toChromosomalInterval(this.getDomain()) ??
|
|
557
|
-
this.getDomain()
|
|
558
|
-
);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/**
|
|
562
|
-
* Return true if the scale is zoomable and the current domain differs from the initial domain.
|
|
563
|
-
*
|
|
564
|
-
* @returns true if zoomed
|
|
565
|
-
*/
|
|
566
|
-
isZoomed() {
|
|
567
|
-
return (
|
|
568
|
-
this.#isZoomingSupported() &&
|
|
569
|
-
shallowArrayEquals(this.#getInitialDomain(), this.getDomain())
|
|
570
|
-
);
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
/**
|
|
574
|
-
* Returns true if zooming is supported and allowed in view spec.
|
|
575
|
-
*/
|
|
576
|
-
isZoomable() {
|
|
577
|
-
// Check explicit configuration
|
|
578
|
-
return this.#isZoomingSupported() && !!this.scale.props.zoom;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
/**
|
|
582
|
-
* Returns true if zooming is supported but not necessarily allowed in view spec.
|
|
583
|
-
*/
|
|
584
|
-
#isZoomingSupported() {
|
|
585
|
-
const type = this.scale.type;
|
|
586
|
-
return isContinuous(type);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
/**
|
|
590
|
-
* Pans (translates) and zooms using a specified scale factor.
|
|
591
|
-
*
|
|
592
|
-
* @param {number} scaleFactor
|
|
593
|
-
* @param {number} scaleAnchor
|
|
594
|
-
* @param {number} pan
|
|
595
|
-
* @returns {boolean} true if the scale was zoomed
|
|
596
|
-
*/
|
|
597
|
-
zoom(scaleFactor, scaleAnchor, pan) {
|
|
598
|
-
if (!this.#isZoomingSupported()) {
|
|
599
|
-
return false;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const scale = this.scale;
|
|
603
|
-
const oldDomain = scale.domain();
|
|
604
|
-
let newDomain = [...oldDomain];
|
|
605
|
-
|
|
606
|
-
/** @type {number} */
|
|
607
|
-
// @ts-ignore
|
|
608
|
-
let anchor = scale.invert(scaleAnchor);
|
|
609
|
-
|
|
610
|
-
if (scale.props.reverse) {
|
|
611
|
-
pan = -pan;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
if ("align" in scale) {
|
|
615
|
-
anchor += scale.align();
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// TODO: symlog
|
|
619
|
-
switch (scale.type) {
|
|
620
|
-
case "linear":
|
|
621
|
-
case "index":
|
|
622
|
-
case "locus":
|
|
623
|
-
newDomain = panLinear(newDomain, pan || 0);
|
|
624
|
-
newDomain = zoomLinear(newDomain, anchor, scaleFactor);
|
|
625
|
-
break;
|
|
626
|
-
case "log":
|
|
627
|
-
newDomain = panLog(newDomain, pan || 0);
|
|
628
|
-
newDomain = zoomLog(newDomain, anchor, scaleFactor);
|
|
629
|
-
break;
|
|
630
|
-
case "pow":
|
|
631
|
-
case "sqrt": {
|
|
632
|
-
const powScale =
|
|
633
|
-
/** @type {import("d3-scale").ScalePower<number, number>} */ (
|
|
634
|
-
scale
|
|
635
|
-
);
|
|
636
|
-
newDomain = panPow(newDomain, pan || 0, powScale.exponent());
|
|
637
|
-
newDomain = zoomPow(
|
|
638
|
-
newDomain,
|
|
639
|
-
anchor,
|
|
640
|
-
scaleFactor,
|
|
641
|
-
powScale.exponent()
|
|
642
|
-
);
|
|
643
|
-
break;
|
|
644
|
-
}
|
|
645
|
-
default:
|
|
646
|
-
throw new Error(
|
|
647
|
-
"Zooming is not implemented for: " + scale.type
|
|
648
|
-
);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
// TODO: Use the zoomTo method. Move clamping etc there.
|
|
652
|
-
const zoomExtent = this.zoomExtent;
|
|
653
|
-
newDomain = clampRange(newDomain, zoomExtent[0], zoomExtent[1]);
|
|
654
|
-
|
|
655
|
-
if ([0, 1].some((i) => newDomain[i] != oldDomain[i])) {
|
|
656
|
-
scale.domain(newDomain);
|
|
657
|
-
this.#notifyListeners("domain");
|
|
658
|
-
return true;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
return false;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
* Immediately zooms to the given interval.
|
|
666
|
-
*
|
|
667
|
-
* @param {NumericDomain | ComplexDomain} domain
|
|
668
|
-
* @param {boolean | number} [duration] an approximate duration for transition.
|
|
669
|
-
* Zero duration zooms immediately. Boolean `true` indicates a default duration.
|
|
670
|
-
*/
|
|
671
|
-
async zoomTo(domain, duration = false) {
|
|
672
|
-
if (isBoolean(duration)) {
|
|
673
|
-
duration = duration ? 700 : 0;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
if (!this.#isZoomingSupported()) {
|
|
677
|
-
throw new Error("Not a zoomable scale!");
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
const to = this.fromComplexInterval(domain);
|
|
681
|
-
|
|
682
|
-
// TODO: Intersect the domain with zoom extent
|
|
683
|
-
|
|
684
|
-
const animator = this.#viewContext.animator;
|
|
685
|
-
|
|
686
|
-
const scale = this.scale;
|
|
687
|
-
const from = /** @type {number[]} */ (scale.domain());
|
|
688
|
-
|
|
689
|
-
if (duration > 0 && from.length == 2) {
|
|
690
|
-
// Spans
|
|
691
|
-
const fw = from[1] - from[0];
|
|
692
|
-
const tw = to[1] - to[0];
|
|
693
|
-
|
|
694
|
-
// Centers
|
|
695
|
-
const fc = from[0] + fw / 2;
|
|
696
|
-
const tc = to[0] + tw / 2;
|
|
697
|
-
|
|
698
|
-
// Constant endpoints. Skip calculation to maintain precision.
|
|
699
|
-
const ac = from[0] == to[0];
|
|
700
|
-
const bc = from[1] == to[1];
|
|
701
|
-
|
|
702
|
-
// TODO: Abort possible previous transition
|
|
703
|
-
await animator.transition({
|
|
704
|
-
duration,
|
|
705
|
-
easingFunction: easeCubicInOut,
|
|
706
|
-
onUpdate: (t) => {
|
|
707
|
-
const w = eerp(fw, tw, t);
|
|
708
|
-
const wt = fw == tw ? t : (fw - w) / (fw - tw);
|
|
709
|
-
const c = wt * tc + (1 - wt) * fc;
|
|
710
|
-
const domain = [
|
|
711
|
-
ac ? from[0] : c - w / 2,
|
|
712
|
-
bc ? from[1] : c + w / 2,
|
|
713
|
-
];
|
|
714
|
-
scale.domain(domain);
|
|
715
|
-
this.#notifyListeners("domain");
|
|
716
|
-
},
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
scale.domain(to);
|
|
720
|
-
this.#notifyListeners("domain");
|
|
721
|
-
} else {
|
|
722
|
-
scale.domain(to);
|
|
723
|
-
animator?.requestRender();
|
|
724
|
-
this.#notifyListeners("domain");
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
/**
|
|
729
|
-
* Resets the current domain to the initial one
|
|
730
|
-
*
|
|
731
|
-
* @returns true if the domain was changed
|
|
732
|
-
*/
|
|
733
|
-
resetZoom() {
|
|
734
|
-
if (!this.#isZoomingSupported()) {
|
|
735
|
-
throw new Error("Not a zoomable scale!");
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
const oldDomain = this.getDomain();
|
|
739
|
-
const newDomain = this.#getInitialDomain();
|
|
740
|
-
|
|
741
|
-
if ([0, 1].some((i) => newDomain[i] != oldDomain[i])) {
|
|
742
|
-
this.#scale.domain(newDomain);
|
|
743
|
-
this.#notifyListeners("domain");
|
|
744
|
-
return true;
|
|
745
|
-
}
|
|
746
|
-
return false;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
/**
|
|
750
|
-
* Returns the zoom level with respect to the reference domain span (the original domain).
|
|
751
|
-
*
|
|
752
|
-
* In principle, this is highly specific to positional channels. However, zooming can
|
|
753
|
-
* be generalized to other quantitative channels such as color, opacity, size, etc.
|
|
754
|
-
*/
|
|
755
|
-
getZoomLevel() {
|
|
756
|
-
// Zoom level makes sense only for user-zoomable scales where zoom extent is defined
|
|
757
|
-
if (this.isZoomable()) {
|
|
758
|
-
return span(this.zoomExtent) / span(this.scale.domain());
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
return 1.0;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
/**
|
|
765
|
-
* Returns the length of the axis in pixels. Chooses the smallest of the views.
|
|
766
|
-
* They should all be the same, but some exotic configuration might break that assumption.
|
|
767
|
-
*
|
|
768
|
-
* This method is needed because positional channels have unit ranges and the
|
|
769
|
-
* length of the axis is not directly available from the scale. Ideally, ranges would
|
|
770
|
-
* be configured as pixels, but that is yet to be materialized.
|
|
771
|
-
*/
|
|
772
|
-
getAxisLength() {
|
|
773
|
-
if (this.channel !== "x" && this.channel !== "y") {
|
|
774
|
-
throw new Error(
|
|
775
|
-
"Axis length is only defined for x and y channels!"
|
|
776
|
-
);
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
// Here's a problem: if the view has been hidden, it may have stale coords.
|
|
780
|
-
// TODO: They should be cleared when the layout is invalidated.
|
|
781
|
-
// Alternatively, scale ranges could be set in pixels.
|
|
782
|
-
const lengths = this.members
|
|
783
|
-
.map(
|
|
784
|
-
(m) =>
|
|
785
|
-
m.view.coords?.[this.channel === "x" ? "width" : "height"]
|
|
786
|
-
)
|
|
787
|
-
.filter((len) => len > 0);
|
|
788
|
-
|
|
789
|
-
return lengths.length
|
|
790
|
-
? lengths.reduce((a, b) => Math.min(a, b), 10000)
|
|
791
|
-
: 0;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
/**
|
|
795
|
-
* @returns {number[]}
|
|
796
|
-
*/
|
|
797
|
-
#getZoomExtent() {
|
|
798
|
-
const props = this.scale.props;
|
|
799
|
-
const zoom = props.zoom;
|
|
800
|
-
|
|
801
|
-
if (isZoomParams(zoom)) {
|
|
802
|
-
if (isArray(zoom.extent)) {
|
|
803
|
-
return this.fromComplexInterval(zoom.extent);
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
if (zoom) {
|
|
808
|
-
if (props.type == "locus") {
|
|
809
|
-
return this.getGenome().getExtent();
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
// TODO: Perhaps this should be "domain" for index scale and nothing for quantitative.
|
|
814
|
-
// Would behave similarly to Vega-Lite, which doesn't have constraints.
|
|
815
|
-
return this.#initialDomain;
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
/**
|
|
819
|
-
* TODO: These actually depend on the mark, so this is clearly a wrong place.
|
|
820
|
-
* And besides, these should be configurable (themeable)
|
|
821
|
-
*
|
|
822
|
-
* @param {string} dataType
|
|
823
|
-
*/
|
|
824
|
-
#getDefaultScaleProperties(dataType) {
|
|
825
|
-
const channel = this.channel;
|
|
826
|
-
const props = {};
|
|
827
|
-
|
|
828
|
-
if (this.#isExplicitDomain()) {
|
|
829
|
-
props.zero = false;
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
if (isPositionalChannel(channel)) {
|
|
833
|
-
props.nice = !this.#isExplicitDomain();
|
|
834
|
-
} else if (isColorChannel(channel)) {
|
|
835
|
-
// TODO: Named ranges
|
|
836
|
-
props.scheme =
|
|
837
|
-
dataType == NOMINAL
|
|
838
|
-
? "tableau10"
|
|
839
|
-
: dataType == ORDINAL
|
|
840
|
-
? "blues"
|
|
841
|
-
: "viridis";
|
|
842
|
-
} else if (isDiscreteChannel(channel)) {
|
|
843
|
-
// Shapes of point mark, for example
|
|
844
|
-
props.range =
|
|
845
|
-
channel == "shape"
|
|
846
|
-
? ["circle", "square", "triangle-up", "cross", "diamond"]
|
|
847
|
-
: [];
|
|
848
|
-
} else if (channel == "size") {
|
|
849
|
-
props.range = [0, 400]; // TODO: Configurable default. This is currently optimized for points.
|
|
850
|
-
} else if (channel == "angle") {
|
|
851
|
-
props.range = [0, 360];
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
return props;
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
/**
|
|
858
|
-
*
|
|
859
|
-
* @returns {import("../genome/genome.js").default}
|
|
860
|
-
*/
|
|
861
|
-
getGenome() {
|
|
862
|
-
if (this.type !== "locus") {
|
|
863
|
-
return undefined;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
// TODO: Support multiple assemblies
|
|
867
|
-
const genome = this.#viewContext.genomeStore?.getGenome();
|
|
868
|
-
if (!genome) {
|
|
869
|
-
throw new Error("No genome has been defined!");
|
|
870
|
-
}
|
|
871
|
-
return genome;
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
// TODO: Move the "complex" stuff into scaleLocus.
|
|
875
|
-
|
|
876
|
-
/**
|
|
877
|
-
* Inverts a value in range to a value on domain. Returns an object in
|
|
878
|
-
* case of locus scale.
|
|
879
|
-
*
|
|
880
|
-
* @param {number} value
|
|
881
|
-
*/
|
|
882
|
-
invertToComplex(value) {
|
|
883
|
-
const scale = this.scale;
|
|
884
|
-
if ("invert" in scale) {
|
|
885
|
-
const inverted = /** @type {number} */ (scale.invert(value));
|
|
886
|
-
return this.toComplex(inverted);
|
|
887
|
-
} else {
|
|
888
|
-
throw new Error("The scale does not support inverting!");
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
/**
|
|
893
|
-
* @param {number} value
|
|
894
|
-
*/
|
|
895
|
-
toComplex(value) {
|
|
896
|
-
const genome = this.getGenome();
|
|
897
|
-
return genome ? genome.toChromosomal(value) : value;
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
/**
|
|
901
|
-
* @param {number | ChromosomalLocus} complex
|
|
902
|
-
* @returns {number}
|
|
903
|
-
*/
|
|
904
|
-
fromComplex(complex) {
|
|
905
|
-
if (isChromosomalLocus(complex)) {
|
|
906
|
-
const genome = this.getGenome();
|
|
907
|
-
return genome.toContinuous(complex.chrom, complex.pos);
|
|
908
|
-
}
|
|
909
|
-
return complex;
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
/**
|
|
913
|
-
* @param {ScalarDomain | ComplexDomain} interval
|
|
914
|
-
* @returns {number[]}
|
|
915
|
-
*/
|
|
916
|
-
fromComplexInterval(interval) {
|
|
917
|
-
if (this.type === "locus" && isChromosomalLocusInterval(interval)) {
|
|
918
|
-
return this.getGenome().toContinuousInterval(interval);
|
|
919
|
-
}
|
|
920
|
-
return /** @type {number[]} */ (interval);
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
/**
|
|
925
|
-
*
|
|
926
|
-
* @param {Channel} channel
|
|
927
|
-
* @param {string} dataType
|
|
928
|
-
* @returns {import("../spec/scale.js").ScaleType}
|
|
929
|
-
*/
|
|
930
|
-
function getDefaultScaleType(channel, dataType) {
|
|
931
|
-
// TODO: Band scale, Bin-Quantitative
|
|
932
|
-
|
|
933
|
-
if (dataType == INDEX || dataType == LOCUS) {
|
|
934
|
-
if (isPrimaryPositionalChannel(channel)) {
|
|
935
|
-
return dataType;
|
|
936
|
-
} else {
|
|
937
|
-
// TODO: Also explicitly set scales should be validated
|
|
938
|
-
throw new Error(
|
|
939
|
-
`${channel} does not support ${dataType} data type. Only positional channels do.`
|
|
940
|
-
);
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
/**
|
|
945
|
-
* @type {Partial<Record<Channel, (import("../spec/scale.js").ScaleType | undefined)[]>>}
|
|
946
|
-
* Default types: nominal, ordinal, quantitative.
|
|
947
|
-
* undefined = incompatible, "null" = disabled (pass-thru)
|
|
948
|
-
*/
|
|
949
|
-
const defaults = {
|
|
950
|
-
x: ["band", "band", "linear"],
|
|
951
|
-
y: ["band", "band", "linear"],
|
|
952
|
-
size: [undefined, "point", "linear"],
|
|
953
|
-
opacity: [undefined, "point", "linear"],
|
|
954
|
-
fillOpacity: [undefined, "point", "linear"],
|
|
955
|
-
strokeOpacity: [undefined, "point", "linear"],
|
|
956
|
-
color: ["ordinal", "ordinal", "linear"],
|
|
957
|
-
fill: ["ordinal", "ordinal", "linear"],
|
|
958
|
-
stroke: ["ordinal", "ordinal", "linear"],
|
|
959
|
-
strokeWidth: [undefined, undefined, "linear"],
|
|
960
|
-
shape: ["ordinal", "ordinal", undefined],
|
|
961
|
-
dx: [undefined, undefined, "null"],
|
|
962
|
-
dy: [undefined, undefined, "null"],
|
|
963
|
-
angle: [undefined, undefined, "linear"],
|
|
964
|
-
sample: ["null", undefined, undefined],
|
|
965
|
-
};
|
|
966
|
-
|
|
967
|
-
/** @type {Channel[]} */
|
|
968
|
-
const typelessChannels = ["sample"];
|
|
969
|
-
|
|
970
|
-
const type = typelessChannels.includes(channel)
|
|
971
|
-
? "null"
|
|
972
|
-
: defaults[channel]
|
|
973
|
-
? defaults[channel][
|
|
974
|
-
[NOMINAL, ORDINAL, QUANTITATIVE].indexOf(dataType)
|
|
975
|
-
]
|
|
976
|
-
: dataType == QUANTITATIVE
|
|
977
|
-
? "linear"
|
|
978
|
-
: "ordinal";
|
|
979
|
-
|
|
980
|
-
if (type === undefined) {
|
|
981
|
-
throw new Error(
|
|
982
|
-
`Channel "${channel}" is not compatible with "${dataType}" data type. Use of a proper scale may be needed.`
|
|
983
|
-
);
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
return type;
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
/**
|
|
990
|
-
* @param {import("../spec/scale.js").Scale} props
|
|
991
|
-
* @param {import("../spec/channel.js").Channel} channel
|
|
992
|
-
*/
|
|
993
|
-
function applyLockedProperties(props, channel) {
|
|
994
|
-
if (isPositionalChannel(channel) && props.type !== "ordinal") {
|
|
995
|
-
props.range = [0, 1];
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
if (channel == "opacity" && isContinuous(props.type)) {
|
|
999
|
-
props.clamp = true;
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
/**
|
|
1004
|
-
*
|
|
1005
|
-
* @param {boolean | ZoomParams} zoom
|
|
1006
|
-
* @returns {zoom is ZoomParams}
|
|
1007
|
-
*/
|
|
1008
|
-
function isZoomParams(zoom) {
|
|
1009
|
-
return isObject(zoom);
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
/**
|
|
1013
|
-
* Reconfigures scales, starting from the given view.
|
|
1014
|
-
*
|
|
1015
|
-
* TODO: This should be made unnecessary. Collectors should trigger the reconfiguration
|
|
1016
|
-
* for those views that get their data from the collector.
|
|
1017
|
-
*
|
|
1018
|
-
* TODO: This may reconfigure channels that are not affected by the change.
|
|
1019
|
-
* Causes performance issues with domains that are extracted from data.
|
|
1020
|
-
*
|
|
1021
|
-
* @param {import("./view.js").default | import("./view.js").default[]} fromViews
|
|
1022
|
-
*/
|
|
1023
|
-
export function reconfigureScales(fromViews) {
|
|
1024
|
-
/** @type {Set<ScaleResolution>} */
|
|
1025
|
-
const uniqueResolutions = new Set();
|
|
1026
|
-
|
|
1027
|
-
/** @param {import("./view.js").default} view */
|
|
1028
|
-
function collectResolutions(view) {
|
|
1029
|
-
for (const resolution of Object.values(view.resolutions.scale)) {
|
|
1030
|
-
uniqueResolutions.add(resolution);
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
for (const fromView of asArray(fromViews)) {
|
|
1035
|
-
// Descendants
|
|
1036
|
-
fromView.visit(collectResolutions);
|
|
1037
|
-
|
|
1038
|
-
// Ancestors
|
|
1039
|
-
for (const view of fromView.getDataAncestors()) {
|
|
1040
|
-
// Skip axis views etc. They should not mess with the domains.
|
|
1041
|
-
if (!view.options.contributesToScaleDomain) {
|
|
1042
|
-
break;
|
|
1043
|
-
}
|
|
1044
|
-
collectResolutions(view);
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
uniqueResolutions.forEach((resolution) => resolution.reconfigure());
|
|
1049
|
-
}
|