@genome-spy/core 0.59.0 → 0.60.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.
Files changed (85) hide show
  1. package/dist/bundle/index.es.js +6100 -5545
  2. package/dist/bundle/index.js +144 -119
  3. package/dist/schema.json +304 -22
  4. package/dist/src/data/collector.d.ts.map +1 -1
  5. package/dist/src/data/collector.js +1 -0
  6. package/dist/src/data/sources/dataUtils.d.ts +2 -1
  7. package/dist/src/data/sources/dataUtils.d.ts.map +1 -1
  8. package/dist/src/data/sources/dataUtils.js +3 -4
  9. package/dist/src/data/sources/inlineSource.d.ts +8 -0
  10. package/dist/src/data/sources/inlineSource.d.ts.map +1 -1
  11. package/dist/src/data/sources/inlineSource.js +17 -1
  12. package/dist/src/data/sources/urlSource.d.ts +1 -0
  13. package/dist/src/data/sources/urlSource.d.ts.map +1 -1
  14. package/dist/src/data/sources/urlSource.js +33 -4
  15. package/dist/src/encoder/encoder.d.ts +1 -1
  16. package/dist/src/genomeSpy.d.ts.map +1 -1
  17. package/dist/src/genomeSpy.js +39 -6
  18. package/dist/src/gl/colorUtils.d.ts +4 -0
  19. package/dist/src/gl/colorUtils.d.ts.map +1 -1
  20. package/dist/src/gl/colorUtils.js +8 -0
  21. package/dist/src/gl/glslScaleGenerator.d.ts +1 -1
  22. package/dist/src/gl/glslScaleGenerator.d.ts.map +1 -1
  23. package/dist/src/gl/glslScaleGenerator.js +1 -9
  24. package/dist/src/gl/includes/common.glsl.js +1 -1
  25. package/dist/src/marks/link.d.ts.map +1 -1
  26. package/dist/src/marks/link.js +8 -0
  27. package/dist/src/marks/mark.d.ts +8 -0
  28. package/dist/src/marks/mark.d.ts.map +1 -1
  29. package/dist/src/marks/mark.js +101 -3
  30. package/dist/src/marks/point.fragment.glsl.js +1 -1
  31. package/dist/src/marks/point.vertex.glsl.js +1 -1
  32. package/dist/src/marks/rect.common.glsl.js +1 -1
  33. package/dist/src/marks/rect.d.ts.map +1 -1
  34. package/dist/src/marks/rect.fragment.glsl.js +1 -1
  35. package/dist/src/marks/rect.js +41 -0
  36. package/dist/src/marks/rect.vertex.glsl.js +1 -1
  37. package/dist/src/selection/selection.d.ts +27 -2
  38. package/dist/src/selection/selection.d.ts.map +1 -1
  39. package/dist/src/selection/selection.js +53 -3
  40. package/dist/src/spec/data.d.ts +18 -1
  41. package/dist/src/spec/mark.d.ts +58 -1
  42. package/dist/src/spec/parameter.d.ts +71 -31
  43. package/dist/src/spec/view.d.ts +9 -2
  44. package/dist/src/styles/genome-spy.css.d.ts +1 -1
  45. package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
  46. package/dist/src/styles/genome-spy.css.js +12 -1
  47. package/dist/src/styles/genome-spy.scss +19 -1
  48. package/dist/src/types/selectionTypes.d.ts +4 -7
  49. package/dist/src/utils/expression.d.ts.map +1 -1
  50. package/dist/src/utils/expression.js +4 -0
  51. package/dist/src/utils/ui/tooltip.d.ts +6 -10
  52. package/dist/src/utils/ui/tooltip.d.ts.map +1 -1
  53. package/dist/src/utils/ui/tooltip.js +74 -42
  54. package/dist/src/view/concatView.d.ts +1 -1
  55. package/dist/src/view/concatView.d.ts.map +1 -1
  56. package/dist/src/view/concatView.js +1 -1
  57. package/dist/src/view/gridView/gridChild.d.ts +53 -0
  58. package/dist/src/view/gridView/gridChild.d.ts.map +1 -0
  59. package/dist/src/view/gridView/gridChild.js +758 -0
  60. package/dist/src/view/gridView/gridView.d.ts +64 -0
  61. package/dist/src/view/gridView/gridView.d.ts.map +1 -0
  62. package/dist/src/view/{gridView.js → gridView/gridView.js} +40 -595
  63. package/dist/src/view/gridView/scrollbar.d.ts +32 -0
  64. package/dist/src/view/gridView/scrollbar.d.ts.map +1 -0
  65. package/dist/src/view/gridView/scrollbar.js +186 -0
  66. package/dist/src/view/gridView/selectionRect.d.ts +10 -0
  67. package/dist/src/view/gridView/selectionRect.d.ts.map +1 -0
  68. package/dist/src/view/gridView/selectionRect.js +182 -0
  69. package/dist/src/view/layout/rectangle.d.ts +11 -1
  70. package/dist/src/view/layout/rectangle.d.ts.map +1 -1
  71. package/dist/src/view/layout/rectangle.js +22 -2
  72. package/dist/src/view/layout/rectangle.test.js +12 -0
  73. package/dist/src/view/paramMediator.d.ts.map +1 -1
  74. package/dist/src/view/paramMediator.js +9 -0
  75. package/dist/src/view/scaleResolution.d.ts +1 -0
  76. package/dist/src/view/scaleResolution.d.ts.map +1 -1
  77. package/dist/src/view/scaleResolution.js +43 -33
  78. package/dist/src/view/view.d.ts +6 -0
  79. package/dist/src/view/view.d.ts.map +1 -1
  80. package/dist/src/view/view.js +19 -0
  81. package/dist/src/view/viewFactory.d.ts.map +1 -1
  82. package/dist/src/view/viewFactory.js +13 -1
  83. package/package.json +2 -2
  84. package/dist/src/view/gridView.d.ts +0 -135
  85. package/dist/src/view/gridView.d.ts.map +0 -1
@@ -1,24 +1,21 @@
1
1
  /* eslint-disable max-depth */
2
- import { primaryPositionalChannels } from "../encoder/encoder.js";
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 "./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 AxisGridView from "./axisGridView.js";
14
- import AxisView, { CHANNEL_ORIENTS, ORIENT_CHANNELS } from "./axisView.js";
15
- import ContainerView from "./containerView.js";
16
- import LayerView from "./layerView.js";
17
- import createTitle from "./title.js";
18
- import UnitView from "./unitView.js";
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("./view.js").default} View
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("../spec/channel.js").PrimaryPositionalChannel, AxisView>> } }
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("../spec/view.js").AnyConcatSpec} spec
71
- * @param {import("../types/viewContext.js").default} context
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("./view.js").ViewOptions} [options]
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("./layout/flexLayout.js").SizeDef[]} */
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("./layout/flexLayout.js").SizeDef}
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("../spec/axis.js").AxisOrient} orient
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("./renderingContext/viewRenderingContext.js").default} context
435
- * @param {import("./layout/rectangle.js").default} coords
436
- * @param {import("../types/rendering.js").RenderingOptions} [options]
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("../utils/interactionEvent.js").default} event
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("./layout/rectangle.js").default} coords Coordinates
736
+ * @param {import("../layout/rectangle.js").default} coords Coordinates
731
737
  * @param {View} view
732
- * @param {import("./zoom.js").ZoomEvent} zoomEvent
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("../spec/channel.js").Channel} channel
767
- * @param {import("../spec/view.js").ResolutionTarget} resolutionType
768
- * @returns {import("../spec/view.js").ResolutionBehavior}
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("../spec/channel.js").PrimaryPositionalChannel, Set<import("./scaleResolution.js").default>>} */
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("./layout/rectangle.js").default} coords
895
- * @param {import("../spec/axis.js").AxisOrient} orient
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
- }