@genome-spy/core 0.58.1 → 0.60.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/bundle/{index-DwLfOHEk.js → index-5ajWdKly.js} +1 -1
- package/dist/bundle/{index-vgGDWUPz.js → index-B03-Om4z.js} +1 -1
- package/dist/bundle/index-Bg7C4Xat.js +2750 -0
- package/dist/bundle/{index-CalimFw3.js → index-C3QR8Lv6.js} +79 -79
- package/dist/bundle/{index-DKe9Bhvi.js → index-g8iXgW0W.js} +1 -1
- package/dist/bundle/index.es.js +6554 -6011
- package/dist/bundle/index.js +189 -164
- package/dist/bundle/{long-BviWyoZx.js → long-B-FASCSo.js} +45 -45
- package/dist/schema.json +312 -25
- package/dist/src/data/collector.d.ts.map +1 -1
- package/dist/src/data/collector.js +1 -0
- package/dist/src/data/flowNode.d.ts.map +1 -1
- package/dist/src/data/sources/dataSource.d.ts.map +1 -1
- package/dist/src/data/sources/dataUtils.d.ts +2 -1
- package/dist/src/data/sources/dataUtils.d.ts.map +1 -1
- package/dist/src/data/sources/dataUtils.js +3 -4
- package/dist/src/data/sources/inlineSource.d.ts +8 -0
- package/dist/src/data/sources/inlineSource.d.ts.map +1 -1
- package/dist/src/data/sources/inlineSource.js +17 -1
- package/dist/src/data/sources/urlSource.d.ts +1 -0
- package/dist/src/data/sources/urlSource.d.ts.map +1 -1
- package/dist/src/data/sources/urlSource.js +33 -4
- package/dist/src/data/transforms/identifier.d.ts.map +1 -1
- package/dist/src/data/transforms/measureText.js +1 -1
- package/dist/src/data/transforms/regexFold.d.ts.map +1 -1
- package/dist/src/data/transforms/regexFold.js +10 -0
- package/dist/src/data/transforms/regexFold.test.js +13 -0
- package/dist/src/encoder/encoder.d.ts +1 -1
- package/dist/src/fonts/bmFontManager.js +2 -2
- package/dist/src/fonts/bmFontMetrics.d.ts.map +1 -1
- package/dist/src/genomeSpy.d.ts.map +1 -1
- package/dist/src/genomeSpy.js +39 -19
- package/dist/src/gl/arrayBuilder.d.ts.map +1 -1
- package/dist/src/gl/colorUtils.d.ts +4 -0
- package/dist/src/gl/colorUtils.d.ts.map +1 -1
- package/dist/src/gl/colorUtils.js +8 -0
- package/dist/src/gl/glslScaleGenerator.d.ts +1 -1
- package/dist/src/gl/glslScaleGenerator.d.ts.map +1 -1
- package/dist/src/gl/glslScaleGenerator.js +1 -9
- package/dist/src/gl/includes/common.glsl.js +1 -1
- package/dist/src/gl/webGLHelper.d.ts +1 -1
- package/dist/src/gl/webGLHelper.d.ts.map +1 -1
- package/dist/src/marks/link.d.ts.map +1 -1
- package/dist/src/marks/link.js +9 -1
- package/dist/src/marks/mark.d.ts +8 -0
- package/dist/src/marks/mark.d.ts.map +1 -1
- package/dist/src/marks/mark.js +101 -3
- package/dist/src/marks/point.d.ts +1 -1
- package/dist/src/marks/point.d.ts.map +1 -1
- package/dist/src/marks/point.fragment.glsl.js +1 -1
- package/dist/src/marks/point.vertex.glsl.js +1 -1
- package/dist/src/marks/rect.common.glsl.js +1 -1
- package/dist/src/marks/rect.d.ts.map +1 -1
- package/dist/src/marks/rect.fragment.glsl.js +1 -1
- package/dist/src/marks/rect.js +41 -0
- package/dist/src/marks/rect.vertex.glsl.js +1 -1
- package/dist/src/selection/selection.d.ts +27 -2
- package/dist/src/selection/selection.d.ts.map +1 -1
- package/dist/src/selection/selection.js +53 -3
- package/dist/src/spec/data.d.ts +18 -1
- package/dist/src/spec/mark.d.ts +58 -1
- package/dist/src/spec/parameter.d.ts +71 -31
- package/dist/src/spec/sampleView.d.ts +12 -1
- package/dist/src/spec/view.d.ts +9 -2
- package/dist/src/styles/genome-spy.css.d.ts +1 -1
- package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
- package/dist/src/styles/genome-spy.css.js +12 -1
- package/dist/src/styles/genome-spy.scss +19 -1
- package/dist/src/types/selectionTypes.d.ts +4 -7
- package/dist/src/types/viewContext.d.ts +0 -15
- package/dist/src/utils/expression.d.ts.map +1 -1
- package/dist/src/utils/expression.js +4 -0
- package/dist/src/utils/indexer.d.ts +0 -2
- package/dist/src/utils/indexer.d.ts.map +1 -1
- package/dist/src/utils/reservationMap.d.ts +4 -4
- package/dist/src/utils/reservationMap.d.ts.map +1 -1
- package/dist/src/utils/scaleNull.d.ts +0 -2
- package/dist/src/utils/scaleNull.d.ts.map +1 -1
- package/dist/src/utils/trees.d.ts +2 -2
- package/dist/src/utils/ui/tooltip.d.ts +6 -10
- package/dist/src/utils/ui/tooltip.d.ts.map +1 -1
- package/dist/src/utils/ui/tooltip.js +74 -42
- package/dist/src/view/concatView.d.ts +1 -1
- package/dist/src/view/concatView.d.ts.map +1 -1
- package/dist/src/view/concatView.js +1 -1
- package/dist/src/view/gridView/gridChild.d.ts +53 -0
- package/dist/src/view/gridView/gridChild.d.ts.map +1 -0
- package/dist/src/view/gridView/gridChild.js +753 -0
- package/dist/src/view/gridView/gridView.d.ts +64 -0
- package/dist/src/view/gridView/gridView.d.ts.map +1 -0
- package/dist/src/view/{gridView.js → gridView/gridView.js} +40 -595
- package/dist/src/view/gridView/scrollbar.d.ts +32 -0
- package/dist/src/view/gridView/scrollbar.d.ts.map +1 -0
- package/dist/src/view/gridView/scrollbar.js +186 -0
- package/dist/src/view/gridView/selectionRect.d.ts +10 -0
- package/dist/src/view/gridView/selectionRect.d.ts.map +1 -0
- package/dist/src/view/gridView/selectionRect.js +182 -0
- package/dist/src/view/layout/rectangle.d.ts +11 -1
- package/dist/src/view/layout/rectangle.d.ts.map +1 -1
- package/dist/src/view/layout/rectangle.js +22 -2
- package/dist/src/view/layout/rectangle.test.js +12 -0
- package/dist/src/view/paramMediator.d.ts.map +1 -1
- package/dist/src/view/paramMediator.js +11 -2
- package/dist/src/view/scaleResolution.d.ts +1 -0
- package/dist/src/view/scaleResolution.d.ts.map +1 -1
- package/dist/src/view/scaleResolution.js +43 -33
- package/dist/src/view/testUtils.d.ts.map +1 -1
- package/dist/src/view/testUtils.js +0 -4
- package/dist/src/view/view.d.ts +6 -0
- package/dist/src/view/view.d.ts.map +1 -1
- package/dist/src/view/view.js +19 -0
- package/dist/src/view/viewFactory.d.ts.map +1 -1
- package/dist/src/view/viewFactory.js +13 -1
- package/package.json +2 -2
- package/dist/bundle/index-DS2hvLgl.js +0 -3425
- package/dist/src/view/gridView.d.ts +0 -135
- package/dist/src/view/gridView.d.ts.map +0 -1
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
/* eslint-disable max-depth */
|
|
2
|
-
import { primaryPositionalChannels } from "
|
|
2
|
+
import { primaryPositionalChannels } from "../../encoder/encoder.js";
|
|
3
3
|
import {
|
|
4
4
|
FlexDimensions,
|
|
5
5
|
getLargestSize,
|
|
6
6
|
mapToPixelCoords,
|
|
7
7
|
parseSizeDef,
|
|
8
8
|
ZERO_SIZEDEF,
|
|
9
|
-
} from "
|
|
10
|
-
import Grid from "
|
|
11
|
-
import Padding from "
|
|
12
|
-
import Rectangle from "
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import { interactionToZoom } from "./zoom.js";
|
|
20
|
-
import clamp from "../utils/clamp.js";
|
|
21
|
-
import { makeLerpSmoother } from "../utils/animator.js";
|
|
9
|
+
} from "../layout/flexLayout.js";
|
|
10
|
+
import Grid from "../layout/grid.js";
|
|
11
|
+
import Padding from "../layout/padding.js";
|
|
12
|
+
import Rectangle from "../layout/rectangle.js";
|
|
13
|
+
import AxisView, { CHANNEL_ORIENTS, ORIENT_CHANNELS } from "../axisView.js";
|
|
14
|
+
import ContainerView from "../containerView.js";
|
|
15
|
+
import LayerView from "../layerView.js";
|
|
16
|
+
import UnitView from "../unitView.js";
|
|
17
|
+
import { interactionToZoom } from "../zoom.js";
|
|
18
|
+
import GridChild from "./gridChild.js";
|
|
22
19
|
|
|
23
20
|
/**
|
|
24
21
|
* Modeled after: https://vega.github.io/vega/docs/layout/
|
|
@@ -41,7 +38,7 @@ export default class GridView extends ContainerView {
|
|
|
41
38
|
* @typedef {"row" | "column"} Direction
|
|
42
39
|
* @typedef {"horizontal" | "vertical"} ScrollDirection
|
|
43
40
|
*
|
|
44
|
-
* @typedef {import("
|
|
41
|
+
* @typedef {import("../view.js").default} View
|
|
45
42
|
*/
|
|
46
43
|
|
|
47
44
|
/** */
|
|
@@ -59,7 +56,7 @@ export default class GridView extends ContainerView {
|
|
|
59
56
|
* toggleable view visibilities. For example, if the bottom view is suddenly hidden,
|
|
60
57
|
* the axis should be shown in the view that takes its place as the new bottom view.
|
|
61
58
|
*
|
|
62
|
-
* @type { Partial<Record<import("
|
|
59
|
+
* @type { Partial<Record<import("../../spec/channel.js").PrimaryPositionalChannel, AxisView>> } }
|
|
63
60
|
*/
|
|
64
61
|
#sharedAxes = {};
|
|
65
62
|
|
|
@@ -67,13 +64,13 @@ export default class GridView extends ContainerView {
|
|
|
67
64
|
|
|
68
65
|
/**
|
|
69
66
|
*
|
|
70
|
-
* @param {import("
|
|
71
|
-
* @param {import("
|
|
67
|
+
* @param {import("../../spec/view.js").AnyConcatSpec} spec
|
|
68
|
+
* @param {import("../../types/viewContext.js").default} context
|
|
72
69
|
* @param {ContainerView} layoutParent
|
|
73
70
|
* @param {View} dataParent
|
|
74
71
|
* @param {string} name
|
|
75
72
|
* @param {number} columns
|
|
76
|
-
* @param {import("
|
|
73
|
+
* @param {import("../view.js").ViewOptions} [options]
|
|
77
74
|
*/
|
|
78
75
|
constructor(
|
|
79
76
|
spec,
|
|
@@ -274,7 +271,7 @@ export default class GridView extends ContainerView {
|
|
|
274
271
|
#makeFlexItems(direction) {
|
|
275
272
|
const sizes = this.#getSizes(direction);
|
|
276
273
|
|
|
277
|
-
/** @type {import("
|
|
274
|
+
/** @type {import("../layout/flexLayout.js").SizeDef[]} */
|
|
278
275
|
const items = [];
|
|
279
276
|
|
|
280
277
|
// Title
|
|
@@ -311,7 +308,7 @@ export default class GridView extends ContainerView {
|
|
|
311
308
|
|
|
312
309
|
/**
|
|
313
310
|
* @param {Direction} direction
|
|
314
|
-
* @return {import("
|
|
311
|
+
* @return {import("../layout/flexLayout.js").SizeDef}
|
|
315
312
|
*/
|
|
316
313
|
#getFlexSize(direction) {
|
|
317
314
|
let grow = 0;
|
|
@@ -394,7 +391,7 @@ export default class GridView extends ContainerView {
|
|
|
394
391
|
|
|
395
392
|
#getSharedAxisOverhang() {
|
|
396
393
|
/**
|
|
397
|
-
* @param {import("
|
|
394
|
+
* @param {import("../../spec/axis.js").AxisOrient} orient
|
|
398
395
|
*/
|
|
399
396
|
const getSharedAxisSize = (orient) => {
|
|
400
397
|
const channel = ORIENT_CHANNELS[orient];
|
|
@@ -431,9 +428,9 @@ export default class GridView extends ContainerView {
|
|
|
431
428
|
}
|
|
432
429
|
|
|
433
430
|
/**
|
|
434
|
-
* @param {import("
|
|
435
|
-
* @param {import("
|
|
436
|
-
* @param {import("
|
|
431
|
+
* @param {import("../renderingContext/viewRenderingContext.js").default} context
|
|
432
|
+
* @param {import("../layout/rectangle.js").default} coords
|
|
433
|
+
* @param {import("../../types/rendering.js").RenderingOptions} [options]
|
|
437
434
|
*/
|
|
438
435
|
// eslint-disable-next-line complexity
|
|
439
436
|
render(context, coords, options = {}) {
|
|
@@ -483,6 +480,7 @@ export default class GridView extends ContainerView {
|
|
|
483
480
|
background,
|
|
484
481
|
backgroundStroke,
|
|
485
482
|
title,
|
|
483
|
+
selectionRect,
|
|
486
484
|
} = gridChild;
|
|
487
485
|
|
|
488
486
|
const [col, row] = grid.getCellCoords(i);
|
|
@@ -661,6 +659,8 @@ export default class GridView extends ContainerView {
|
|
|
661
659
|
view.render(context, viewCoords, options);
|
|
662
660
|
}
|
|
663
661
|
|
|
662
|
+
selectionRect?.render(context, viewCoords, options);
|
|
663
|
+
|
|
664
664
|
for (const scrollbar of Object.values(gridChild.scrollbars)) {
|
|
665
665
|
scrollbar.updateScrollbar(viewportCoords, viewCoords);
|
|
666
666
|
scrollbar.render(context, coords, options);
|
|
@@ -673,7 +673,7 @@ export default class GridView extends ContainerView {
|
|
|
673
673
|
}
|
|
674
674
|
|
|
675
675
|
/**
|
|
676
|
-
* @param {import("
|
|
676
|
+
* @param {import("../../utils/interactionEvent.js").default} event
|
|
677
677
|
*/
|
|
678
678
|
propagateInteractionEvent(event) {
|
|
679
679
|
this.handleInteractionEvent(undefined, event, true);
|
|
@@ -699,6 +699,12 @@ export default class GridView extends ContainerView {
|
|
|
699
699
|
if (pointedView) {
|
|
700
700
|
pointedView.propagateInteractionEvent(event);
|
|
701
701
|
|
|
702
|
+
if (event.stopped) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Hmm, maybe this should be registered when needed and not include
|
|
707
|
+
// as a hardcoded interaction?
|
|
702
708
|
if (
|
|
703
709
|
pointedView instanceof UnitView ||
|
|
704
710
|
pointedView instanceof LayerView
|
|
@@ -727,9 +733,9 @@ export default class GridView extends ContainerView {
|
|
|
727
733
|
|
|
728
734
|
/**
|
|
729
735
|
*
|
|
730
|
-
* @param {import("
|
|
736
|
+
* @param {import("../layout/rectangle.js").default} coords Coordinates
|
|
731
737
|
* @param {View} view
|
|
732
|
-
* @param {import("
|
|
738
|
+
* @param {import("../zoom.js").ZoomEvent} zoomEvent
|
|
733
739
|
*/
|
|
734
740
|
#handleZoom(coords, view, zoomEvent) {
|
|
735
741
|
for (const [channel, resolutionSet] of Object.entries(
|
|
@@ -763,99 +769,22 @@ export default class GridView extends ContainerView {
|
|
|
763
769
|
}
|
|
764
770
|
|
|
765
771
|
/**
|
|
766
|
-
* @param {import("
|
|
767
|
-
* @param {import("
|
|
768
|
-
* @returns {import("
|
|
772
|
+
* @param {import("../../spec/channel.js").Channel} channel
|
|
773
|
+
* @param {import("../../spec/view.js").ResolutionTarget} resolutionType
|
|
774
|
+
* @returns {import("../../spec/view.js").ResolutionBehavior}
|
|
769
775
|
*/
|
|
770
776
|
getDefaultResolution(channel, resolutionType) {
|
|
771
777
|
return "independent";
|
|
772
778
|
}
|
|
773
779
|
}
|
|
774
780
|
|
|
775
|
-
/**
|
|
776
|
-
* @param {import("../spec/view.js").ViewBackground} viewBackground
|
|
777
|
-
* @returns {import("../spec/view.js").UnitSpec}
|
|
778
|
-
*/
|
|
779
|
-
export function createBackground(viewBackground) {
|
|
780
|
-
if (
|
|
781
|
-
!viewBackground ||
|
|
782
|
-
!viewBackground.fill ||
|
|
783
|
-
viewBackground.fillOpacity === 0
|
|
784
|
-
) {
|
|
785
|
-
return;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
return {
|
|
789
|
-
configurableVisibility: false,
|
|
790
|
-
data: { values: [{}] },
|
|
791
|
-
mark: {
|
|
792
|
-
color: viewBackground.fill,
|
|
793
|
-
opacity: viewBackground.fillOpacity ?? 1.0,
|
|
794
|
-
type: "rect",
|
|
795
|
-
clip: false, // Shouldn't be needed
|
|
796
|
-
tooltip: null,
|
|
797
|
-
minHeight: 1,
|
|
798
|
-
minOpacity: 0,
|
|
799
|
-
},
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
/**
|
|
804
|
-
* @param {import("../spec/view.js").ViewBackground} viewBackground
|
|
805
|
-
* @returns {import("../spec/view.js").UnitSpec}
|
|
806
|
-
*/
|
|
807
|
-
export function createBackgroundStroke(viewBackground) {
|
|
808
|
-
if (
|
|
809
|
-
!viewBackground ||
|
|
810
|
-
!viewBackground.stroke ||
|
|
811
|
-
viewBackground.strokeWidth === 0 ||
|
|
812
|
-
viewBackground.strokeOpacity === 0
|
|
813
|
-
) {
|
|
814
|
-
return;
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
// Using rules to draw a non-filled rectangle.
|
|
818
|
-
// We are not using a rect mark because it is not optimized for outlines.
|
|
819
|
-
// TODO: Implement "hollow" mesh for non-filled rectangles
|
|
820
|
-
return {
|
|
821
|
-
configurableVisibility: false,
|
|
822
|
-
resolve: {
|
|
823
|
-
scale: { x: "excluded", y: "excluded" },
|
|
824
|
-
axis: { x: "excluded", y: "excluded" },
|
|
825
|
-
},
|
|
826
|
-
data: {
|
|
827
|
-
values: [
|
|
828
|
-
{ x: 0, y: 0, x2: 1, y2: 0 },
|
|
829
|
-
{ x: 1, y: 0, x2: 1, y2: 1 },
|
|
830
|
-
{ x: 1, y: 1, x2: 0, y2: 1 },
|
|
831
|
-
{ x: 0, y: 1, x2: 0, y2: 0 },
|
|
832
|
-
],
|
|
833
|
-
},
|
|
834
|
-
mark: {
|
|
835
|
-
size: viewBackground.strokeWidth ?? 1.0,
|
|
836
|
-
color: viewBackground.stroke ?? "lightgray",
|
|
837
|
-
strokeCap: "square",
|
|
838
|
-
opacity: viewBackground.strokeOpacity ?? 1.0,
|
|
839
|
-
type: "rule",
|
|
840
|
-
clip: false,
|
|
841
|
-
tooltip: null,
|
|
842
|
-
},
|
|
843
|
-
encoding: {
|
|
844
|
-
x: { field: "x", type: "quantitative", scale: null },
|
|
845
|
-
y: { field: "y", type: "quantitative", scale: null },
|
|
846
|
-
x2: { field: "x2" },
|
|
847
|
-
y2: { field: "y2" },
|
|
848
|
-
},
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
|
|
852
781
|
/**
|
|
853
782
|
*
|
|
854
783
|
* @param {View} view
|
|
855
784
|
* @returns
|
|
856
785
|
*/
|
|
857
786
|
function getZoomableResolutions(view) {
|
|
858
|
-
/** @type {Record<import("
|
|
787
|
+
/** @type {Record<import("../../spec/channel.js").PrimaryPositionalChannel, Set<import("../scaleResolution.js").default>>} */
|
|
859
788
|
const resolutions = {
|
|
860
789
|
x: new Set(),
|
|
861
790
|
y: new Set(),
|
|
@@ -891,8 +820,8 @@ export function isClippedChildren(view) {
|
|
|
891
820
|
|
|
892
821
|
/**
|
|
893
822
|
*
|
|
894
|
-
* @param {import("
|
|
895
|
-
* @param {import("
|
|
823
|
+
* @param {import("../layout/rectangle.js").default} coords
|
|
824
|
+
* @param {import("../../spec/axis.js").AxisOrient} orient
|
|
896
825
|
* @param {AxisView} axisView
|
|
897
826
|
*/
|
|
898
827
|
export function translateAxisCoords(coords, orient, axisView) {
|
|
@@ -913,487 +842,3 @@ export function translateAxisCoords(coords, orient, axisView) {
|
|
|
913
842
|
.modify({ width: ps });
|
|
914
843
|
}
|
|
915
844
|
}
|
|
916
|
-
|
|
917
|
-
export class GridChild {
|
|
918
|
-
/**
|
|
919
|
-
* @param {View} view
|
|
920
|
-
* @param {ContainerView} layoutParent
|
|
921
|
-
* @param {number} serial
|
|
922
|
-
*/
|
|
923
|
-
constructor(view, layoutParent, serial) {
|
|
924
|
-
this.layoutParent = layoutParent;
|
|
925
|
-
this.view = view;
|
|
926
|
-
this.serial = serial;
|
|
927
|
-
|
|
928
|
-
/** @type {UnitView} */
|
|
929
|
-
this.background = undefined;
|
|
930
|
-
|
|
931
|
-
/** @type {UnitView} */
|
|
932
|
-
this.backgroundStroke = undefined;
|
|
933
|
-
|
|
934
|
-
/** @type {Partial<Record<import("../spec/axis.js").AxisOrient, AxisView>>} axes */
|
|
935
|
-
this.axes = {};
|
|
936
|
-
|
|
937
|
-
/** @type {Partial<Record<import("../spec/axis.js").AxisOrient, AxisGridView>>} gridLines */
|
|
938
|
-
this.gridLines = {};
|
|
939
|
-
|
|
940
|
-
/** @type {Partial<Record<ScrollDirection, Scrollbar>>} */
|
|
941
|
-
this.scrollbars = {};
|
|
942
|
-
|
|
943
|
-
/** @type {UnitView} */
|
|
944
|
-
this.title = undefined;
|
|
945
|
-
|
|
946
|
-
/** @type {Rectangle} */
|
|
947
|
-
this.coords = Rectangle.ZERO;
|
|
948
|
-
|
|
949
|
-
if (view.needsAxes.x || view.needsAxes.y) {
|
|
950
|
-
const spec = view.spec;
|
|
951
|
-
const viewBackground = "view" in spec ? spec?.view : undefined;
|
|
952
|
-
|
|
953
|
-
const backgroundSpec = createBackground(viewBackground);
|
|
954
|
-
if (backgroundSpec) {
|
|
955
|
-
this.background = new UnitView(
|
|
956
|
-
backgroundSpec,
|
|
957
|
-
layoutParent.context,
|
|
958
|
-
layoutParent,
|
|
959
|
-
view,
|
|
960
|
-
"background" + serial,
|
|
961
|
-
{
|
|
962
|
-
blockEncodingInheritance: true,
|
|
963
|
-
}
|
|
964
|
-
);
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
const backgroundStrokeSpec = createBackgroundStroke(viewBackground);
|
|
968
|
-
if (backgroundStrokeSpec) {
|
|
969
|
-
this.backgroundStroke = new UnitView(
|
|
970
|
-
backgroundStrokeSpec,
|
|
971
|
-
layoutParent.context,
|
|
972
|
-
layoutParent,
|
|
973
|
-
view,
|
|
974
|
-
"backgroundStroke" + serial,
|
|
975
|
-
{
|
|
976
|
-
blockEncodingInheritance: true,
|
|
977
|
-
}
|
|
978
|
-
);
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
const title = createTitle(view.spec.title);
|
|
982
|
-
if (title) {
|
|
983
|
-
const unitView = new UnitView(
|
|
984
|
-
title,
|
|
985
|
-
layoutParent.context,
|
|
986
|
-
layoutParent,
|
|
987
|
-
view,
|
|
988
|
-
"title" + serial,
|
|
989
|
-
{
|
|
990
|
-
blockEncodingInheritance: true,
|
|
991
|
-
}
|
|
992
|
-
);
|
|
993
|
-
this.title = unitView;
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
// TODO: More specific getter for this
|
|
998
|
-
if (view.spec.viewportWidth != null) {
|
|
999
|
-
this.scrollbars.horizontal = new Scrollbar(this, "horizontal");
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
if (view.spec.viewportHeight != null) {
|
|
1003
|
-
this.scrollbars.vertical = new Scrollbar(this, "vertical");
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
*getChildren() {
|
|
1008
|
-
if (this.background) {
|
|
1009
|
-
yield this.background;
|
|
1010
|
-
}
|
|
1011
|
-
if (this.backgroundStroke) {
|
|
1012
|
-
yield this.backgroundStroke;
|
|
1013
|
-
}
|
|
1014
|
-
if (this.title) {
|
|
1015
|
-
yield this.title;
|
|
1016
|
-
}
|
|
1017
|
-
yield* Object.values(this.axes);
|
|
1018
|
-
yield* Object.values(this.gridLines);
|
|
1019
|
-
yield this.view;
|
|
1020
|
-
yield* Object.values(this.scrollbars);
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
/**
|
|
1024
|
-
* Create view decorations, grid lines, axes, etc.
|
|
1025
|
-
*/
|
|
1026
|
-
async createAxes() {
|
|
1027
|
-
const { view, axes, gridLines } = this;
|
|
1028
|
-
|
|
1029
|
-
/**
|
|
1030
|
-
* @param {import("./axisResolution.js").default} r
|
|
1031
|
-
* @param {import("../spec/channel.js").PrimaryPositionalChannel} channel
|
|
1032
|
-
*/
|
|
1033
|
-
const getAxisPropsWithDefaults = (r, channel) => {
|
|
1034
|
-
const propsWithoutDefaults = r.getAxisProps();
|
|
1035
|
-
if (propsWithoutDefaults === null) {
|
|
1036
|
-
return;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
const props = propsWithoutDefaults
|
|
1040
|
-
? { ...propsWithoutDefaults }
|
|
1041
|
-
: {};
|
|
1042
|
-
|
|
1043
|
-
// Pick a default orient based on what is available.
|
|
1044
|
-
// This logic is needed for layer views that have independent axes.
|
|
1045
|
-
if (!props.orient) {
|
|
1046
|
-
for (const orient of CHANNEL_ORIENTS[channel]) {
|
|
1047
|
-
if (!axes[orient]) {
|
|
1048
|
-
props.orient = orient;
|
|
1049
|
-
break;
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
if (!props.orient) {
|
|
1053
|
-
throw new Error(
|
|
1054
|
-
"No slots available for an axis! Perhaps a LayerView has more than two children?"
|
|
1055
|
-
);
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
props.title ??= r.getTitle();
|
|
1060
|
-
|
|
1061
|
-
if (!CHANNEL_ORIENTS[channel].includes(props.orient)) {
|
|
1062
|
-
throw new Error(
|
|
1063
|
-
`Invalid axis orientation "${props.orient}" on channel "${channel}"!`
|
|
1064
|
-
);
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
return props;
|
|
1068
|
-
};
|
|
1069
|
-
|
|
1070
|
-
/**
|
|
1071
|
-
* @param {import("./axisResolution.js").default} r
|
|
1072
|
-
* @param {import("../spec/channel.js").PrimaryPositionalChannel} channel
|
|
1073
|
-
* @param {View} axisParent
|
|
1074
|
-
*/
|
|
1075
|
-
const createAxis = async (r, channel, axisParent) => {
|
|
1076
|
-
const props = getAxisPropsWithDefaults(r, channel);
|
|
1077
|
-
|
|
1078
|
-
if (props) {
|
|
1079
|
-
if (axes[props.orient]) {
|
|
1080
|
-
throw new Error(
|
|
1081
|
-
`An axis with the orient "${props.orient}" already exists!`
|
|
1082
|
-
);
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
const axisView = new AxisView(
|
|
1086
|
-
props,
|
|
1087
|
-
r.scaleResolution.type,
|
|
1088
|
-
this.layoutParent.context,
|
|
1089
|
-
this.layoutParent,
|
|
1090
|
-
axisParent
|
|
1091
|
-
);
|
|
1092
|
-
axes[props.orient] = axisView;
|
|
1093
|
-
await axisView.initializeChildren();
|
|
1094
|
-
}
|
|
1095
|
-
};
|
|
1096
|
-
|
|
1097
|
-
/**
|
|
1098
|
-
* @param {import("./axisResolution.js").default} r
|
|
1099
|
-
* @param {import("../spec/channel.js").PrimaryPositionalChannel} channel
|
|
1100
|
-
* @param {View} axisParent
|
|
1101
|
-
*/
|
|
1102
|
-
const createAxisGrid = async (r, channel, axisParent) => {
|
|
1103
|
-
const props = getAxisPropsWithDefaults(r, channel);
|
|
1104
|
-
|
|
1105
|
-
if (props && (props.grid || props.chromGrid)) {
|
|
1106
|
-
const axisGridView = new AxisGridView(
|
|
1107
|
-
props,
|
|
1108
|
-
r.scaleResolution.type,
|
|
1109
|
-
this.layoutParent.context,
|
|
1110
|
-
this.layoutParent,
|
|
1111
|
-
axisParent
|
|
1112
|
-
);
|
|
1113
|
-
gridLines[props.orient] = axisGridView;
|
|
1114
|
-
await axisGridView.initializeChildren();
|
|
1115
|
-
}
|
|
1116
|
-
};
|
|
1117
|
-
|
|
1118
|
-
// Handle children that have caught axis resolutions. Create axes for them.
|
|
1119
|
-
for (const channel of /** @type {import("../spec/channel.js").PrimaryPositionalChannel[]} */ ([
|
|
1120
|
-
"x",
|
|
1121
|
-
"y",
|
|
1122
|
-
])) {
|
|
1123
|
-
if (view.needsAxes[channel]) {
|
|
1124
|
-
const r = view.resolutions.axis[channel];
|
|
1125
|
-
if (!r) {
|
|
1126
|
-
continue;
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
await createAxis(r, channel, view);
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
// Handle gridlines of children. Note: children's axis resolution may be caught by
|
|
1134
|
-
// this view or some of this view's ancestors.
|
|
1135
|
-
for (const channel of /** @type {import("../spec/channel.js").PrimaryPositionalChannel[]} */ ([
|
|
1136
|
-
"x",
|
|
1137
|
-
"y",
|
|
1138
|
-
])) {
|
|
1139
|
-
if (
|
|
1140
|
-
view.needsAxes[channel] &&
|
|
1141
|
-
// Handle a special case where the child view has an excluded resolution
|
|
1142
|
-
// but no scale or axis, e.g., when only values are used on a channel.
|
|
1143
|
-
view.getConfiguredOrDefaultResolution(channel, "axis") !=
|
|
1144
|
-
"excluded"
|
|
1145
|
-
) {
|
|
1146
|
-
const r = view.getAxisResolution(channel);
|
|
1147
|
-
if (!r) {
|
|
1148
|
-
continue;
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
await createAxisGrid(r, channel, view);
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
// Handle LayerView's possible independent axes
|
|
1156
|
-
if (view instanceof LayerView) {
|
|
1157
|
-
// First create axes that have an orient preference
|
|
1158
|
-
for (const layerChild of view) {
|
|
1159
|
-
for (const [channel, r] of Object.entries(
|
|
1160
|
-
layerChild.resolutions.axis
|
|
1161
|
-
)) {
|
|
1162
|
-
const props = r.getAxisProps();
|
|
1163
|
-
if (props && props.orient) {
|
|
1164
|
-
await createAxis(r, channel, layerChild);
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
// Then create axes in a priority order
|
|
1170
|
-
for (const layerChild of view) {
|
|
1171
|
-
for (const [channel, r] of Object.entries(
|
|
1172
|
-
layerChild.resolutions.axis
|
|
1173
|
-
)) {
|
|
1174
|
-
const props = r.getAxisProps();
|
|
1175
|
-
if (props && !props.orient) {
|
|
1176
|
-
await createAxis(r, channel, layerChild);
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
// TODO: Axis grid
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
// Axes are created after scales are resolved, so we need to resolve possible new scales here
|
|
1185
|
-
[...Object.values(axes), ...Object.values(gridLines)].forEach((v) =>
|
|
1186
|
-
v.visit((view) => {
|
|
1187
|
-
if (view instanceof UnitView) {
|
|
1188
|
-
view.resolve("scale");
|
|
1189
|
-
}
|
|
1190
|
-
})
|
|
1191
|
-
);
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
getOverhang() {
|
|
1195
|
-
const calculate = (
|
|
1196
|
-
/** @type {import("../spec/axis.js").AxisOrient} */ orient
|
|
1197
|
-
) => {
|
|
1198
|
-
const axisView = this.axes[orient];
|
|
1199
|
-
return axisView
|
|
1200
|
-
? Math.max(
|
|
1201
|
-
axisView.getPerpendicularSize() +
|
|
1202
|
-
(axisView.axisProps.offset ?? 0),
|
|
1203
|
-
0
|
|
1204
|
-
)
|
|
1205
|
-
: 0;
|
|
1206
|
-
};
|
|
1207
|
-
|
|
1208
|
-
// Axes and overhang should be mutually exclusive, so we can just add them together
|
|
1209
|
-
return new Padding(
|
|
1210
|
-
calculate("top"),
|
|
1211
|
-
calculate("right"),
|
|
1212
|
-
calculate("bottom"),
|
|
1213
|
-
calculate("left")
|
|
1214
|
-
).add(this.view.getOverhang());
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
getOverhangAndPadding() {
|
|
1218
|
-
return this.getOverhang().add(this.view.getPadding());
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
class Scrollbar extends UnitView {
|
|
1223
|
-
/** @type {ScrollDirection} */
|
|
1224
|
-
#scrollDirection;
|
|
1225
|
-
|
|
1226
|
-
#scrollbarCoords = Rectangle.ZERO;
|
|
1227
|
-
|
|
1228
|
-
#maxScrollOffset = 0;
|
|
1229
|
-
|
|
1230
|
-
#maxViewportOffset = 0;
|
|
1231
|
-
|
|
1232
|
-
// This is the actual state of the scrollbar. It's better to keep track of
|
|
1233
|
-
// the viewport offset rather than the scrollbar offset because the former
|
|
1234
|
-
// is more stable when the viewport size changes.
|
|
1235
|
-
viewportOffset = 0;
|
|
1236
|
-
|
|
1237
|
-
/**
|
|
1238
|
-
* @param {GridChild} gridChild
|
|
1239
|
-
* @param {ScrollDirection} scrollDirection
|
|
1240
|
-
*/
|
|
1241
|
-
constructor(gridChild, scrollDirection) {
|
|
1242
|
-
// TODO: Configurable
|
|
1243
|
-
const config = {
|
|
1244
|
-
scrollbarSize: 8,
|
|
1245
|
-
scrollbarPadding: 2,
|
|
1246
|
-
// TODO: inside/outside view
|
|
1247
|
-
};
|
|
1248
|
-
|
|
1249
|
-
super(
|
|
1250
|
-
{
|
|
1251
|
-
data: { values: [{}] },
|
|
1252
|
-
mark: {
|
|
1253
|
-
type: "rect",
|
|
1254
|
-
fill: "#b0b0b0",
|
|
1255
|
-
fillOpacity: 0.6,
|
|
1256
|
-
stroke: "white",
|
|
1257
|
-
strokeWidth: 1,
|
|
1258
|
-
strokeOpacity: 1,
|
|
1259
|
-
cornerRadius: 5,
|
|
1260
|
-
clip: false,
|
|
1261
|
-
},
|
|
1262
|
-
configurableVisibility: false,
|
|
1263
|
-
},
|
|
1264
|
-
gridChild.layoutParent.context,
|
|
1265
|
-
gridChild.layoutParent,
|
|
1266
|
-
gridChild.view,
|
|
1267
|
-
"scrollbar-" + scrollDirection, // TODO: Serial
|
|
1268
|
-
{
|
|
1269
|
-
blockEncodingInheritance: true,
|
|
1270
|
-
}
|
|
1271
|
-
);
|
|
1272
|
-
|
|
1273
|
-
this.config = config;
|
|
1274
|
-
this.#scrollDirection = scrollDirection;
|
|
1275
|
-
|
|
1276
|
-
// Make it smooth!
|
|
1277
|
-
this.interpolateViewportOffset = makeLerpSmoother(
|
|
1278
|
-
this.context.animator,
|
|
1279
|
-
(value) => {
|
|
1280
|
-
this.viewportOffset = value.x;
|
|
1281
|
-
},
|
|
1282
|
-
50,
|
|
1283
|
-
0.4,
|
|
1284
|
-
{ x: this.viewportOffset }
|
|
1285
|
-
);
|
|
1286
|
-
|
|
1287
|
-
this.addInteractionEventListener("mousedown", (coords, event) => {
|
|
1288
|
-
event.stopPropagation();
|
|
1289
|
-
|
|
1290
|
-
if (this.#maxScrollOffset <= 0) {
|
|
1291
|
-
return;
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
const getMouseOffset = (/** @type {MouseEvent} */ mouseEvent) =>
|
|
1295
|
-
scrollDirection == "vertical"
|
|
1296
|
-
? mouseEvent.clientY
|
|
1297
|
-
: mouseEvent.clientX;
|
|
1298
|
-
|
|
1299
|
-
const mouseEvent = /** @type {MouseEvent} */ (event.uiEvent);
|
|
1300
|
-
mouseEvent.preventDefault();
|
|
1301
|
-
|
|
1302
|
-
const initialScrollOffset = this.scrollOffset;
|
|
1303
|
-
const initialOffset = getMouseOffset(mouseEvent);
|
|
1304
|
-
|
|
1305
|
-
const onMousemove = /** @param {MouseEvent} moveEvent */ (
|
|
1306
|
-
moveEvent
|
|
1307
|
-
) => {
|
|
1308
|
-
const scrollOffset = clamp(
|
|
1309
|
-
getMouseOffset(moveEvent) -
|
|
1310
|
-
initialOffset +
|
|
1311
|
-
initialScrollOffset,
|
|
1312
|
-
0,
|
|
1313
|
-
this.#maxScrollOffset
|
|
1314
|
-
);
|
|
1315
|
-
|
|
1316
|
-
this.interpolateViewportOffset({
|
|
1317
|
-
x:
|
|
1318
|
-
(scrollOffset / this.#maxScrollOffset) *
|
|
1319
|
-
this.#maxViewportOffset,
|
|
1320
|
-
});
|
|
1321
|
-
};
|
|
1322
|
-
|
|
1323
|
-
const onMouseup = () => {
|
|
1324
|
-
document.removeEventListener("mousemove", onMousemove);
|
|
1325
|
-
document.removeEventListener("mouseup", onMouseup);
|
|
1326
|
-
};
|
|
1327
|
-
|
|
1328
|
-
document.addEventListener("mouseup", onMouseup, false);
|
|
1329
|
-
document.addEventListener("mousemove", onMousemove, false);
|
|
1330
|
-
});
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
get scrollOffset() {
|
|
1334
|
-
return (
|
|
1335
|
-
(this.viewportOffset / this.#maxViewportOffset) *
|
|
1336
|
-
this.#maxScrollOffset
|
|
1337
|
-
);
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
/**
|
|
1341
|
-
* @param {import("./renderingContext/viewRenderingContext.js").default} context
|
|
1342
|
-
* @param {import("./layout/rectangle.js").default} coords
|
|
1343
|
-
* @param {import("../types/rendering.js").RenderingOptions} [options]
|
|
1344
|
-
*/
|
|
1345
|
-
render(context, coords, options) {
|
|
1346
|
-
super.render(context, this.#scrollbarCoords, options);
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
/**
|
|
1350
|
-
*
|
|
1351
|
-
* @param {Rectangle} viewportCoords
|
|
1352
|
-
* @param {Rectangle} coords
|
|
1353
|
-
*/
|
|
1354
|
-
updateScrollbar(viewportCoords, coords) {
|
|
1355
|
-
const sPad = this.config.scrollbarPadding;
|
|
1356
|
-
const sSize = this.config.scrollbarSize;
|
|
1357
|
-
|
|
1358
|
-
const dimension =
|
|
1359
|
-
this.#scrollDirection == "horizontal" ? "width" : "height";
|
|
1360
|
-
|
|
1361
|
-
const visibleFraction = Math.min(
|
|
1362
|
-
1,
|
|
1363
|
-
viewportCoords[dimension] / coords[dimension]
|
|
1364
|
-
);
|
|
1365
|
-
const maxScrollLength = viewportCoords[dimension] - 2 * sPad;
|
|
1366
|
-
const scrollLength = visibleFraction * maxScrollLength;
|
|
1367
|
-
|
|
1368
|
-
this.#maxScrollOffset = maxScrollLength - scrollLength;
|
|
1369
|
-
this.#maxViewportOffset = coords[dimension] - viewportCoords[dimension];
|
|
1370
|
-
this.viewportOffset = clamp(
|
|
1371
|
-
this.viewportOffset,
|
|
1372
|
-
0,
|
|
1373
|
-
this.#maxViewportOffset
|
|
1374
|
-
);
|
|
1375
|
-
|
|
1376
|
-
this.#scrollbarCoords =
|
|
1377
|
-
this.#scrollDirection == "vertical"
|
|
1378
|
-
? new Rectangle(
|
|
1379
|
-
() =>
|
|
1380
|
-
viewportCoords.x +
|
|
1381
|
-
viewportCoords.width -
|
|
1382
|
-
sSize -
|
|
1383
|
-
sPad,
|
|
1384
|
-
() => viewportCoords.y + sPad + this.scrollOffset,
|
|
1385
|
-
() => sSize,
|
|
1386
|
-
() => scrollLength
|
|
1387
|
-
)
|
|
1388
|
-
: new Rectangle(
|
|
1389
|
-
() => viewportCoords.x + sPad + this.scrollOffset,
|
|
1390
|
-
() =>
|
|
1391
|
-
viewportCoords.y +
|
|
1392
|
-
viewportCoords.height -
|
|
1393
|
-
sSize -
|
|
1394
|
-
sPad,
|
|
1395
|
-
() => scrollLength,
|
|
1396
|
-
() => sSize
|
|
1397
|
-
);
|
|
1398
|
-
}
|
|
1399
|
-
}
|