@vitessce/statistical-plots 4.0.8 → 4.0.10

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.
@@ -54,7 +54,8 @@ const ViewType$1 = {
54
54
  TREEMAP: "treemap",
55
55
  SAMPLE_SET_PAIR_MANAGER: "sampleSetPairManager",
56
56
  FEATURE_STATS_TABLE: "featureStatsTable",
57
- SINGLE_OBS_SET_SELECTOR: "singleObsSetSelector"
57
+ SINGLE_OBS_SET_SELECTOR: "singleObsSetSelector",
58
+ ANNOTATION_CONTROLLER: "annotationController"
58
59
  };
59
60
  const DataType$2 = {
60
61
  OBS_LABELS: "obsLabels",
@@ -75,7 +76,8 @@ const DataType$2 = {
75
76
  COMPARISON_METADATA: "comparisonMetadata",
76
77
  FEATURE_STATS: "featureStats",
77
78
  FEATURE_SET_STATS: "featureSetStats",
78
- OBS_SET_STATS: "obsSetStats"
79
+ OBS_SET_STATS: "obsSetStats",
80
+ ANNOTATION_STORY: "annotationStory"
79
81
  };
80
82
  const AsyncFunctionType = {
81
83
  TRANSFORM_FEATURE: "transformFeature"
@@ -100,6 +102,7 @@ const FileType$1 = {
100
102
  OBS_SETS_CSV: "obsSets.csv",
101
103
  OBS_SETS_JSON: "obsSets.json",
102
104
  SAMPLE_SETS_CSV: "sampleSets.csv",
105
+ ANNOTATION_STORY_JSON: "annotationStory.json",
103
106
  // OME-Zarr
104
107
  IMAGE_OME_ZARR: "image.ome-zarr",
105
108
  OBS_SEGMENTATIONS_OME_ZARR: "obsSegmentations.ome-zarr",
@@ -384,7 +387,15 @@ const CoordinationType$1 = {
384
387
  FEATURE_POINT_FOLD_CHANGE_THRESHOLD: "featurePointFoldChangeThreshold",
385
388
  FEATURE_LABEL_FOLD_CHANGE_THRESHOLD: "featureLabelFoldChangeThreshold",
386
389
  // Treemap
387
- HIERARCHY_LEVELS: "hierarchyLevels"
390
+ HIERARCHY_LEVELS: "hierarchyLevels",
391
+ // Annotation frames
392
+ ANNOTATION_STORY: "annotationStory",
393
+ ANNOTATION_FRAME_INDEX: "annotationFrameIndex",
394
+ ANNOTATION_SHAPES: "annotationShapes",
395
+ ANNOTATION_OVERLAY_VISIBLE: "annotationOverlayVisible",
396
+ ANNOTATION_TRANSITION_DURATION: "annotationTransitionDuration",
397
+ ANNOTATION_SEMANTIC_ZOOM: "annotationSemanticZoom",
398
+ ANNOTATION_EDITABLE: "annotationEditable"
388
399
  };
389
400
  const ViewHelpMapping = {
390
401
  OBS_SET_SIZES: "The observation set sizes view displays a bar plot with the currently-selected observation sets (e.g., cell types) on the x-axis and bars representing their size (e.g., number of cells) on the y-axis.",
@@ -412,6 +423,7 @@ const ViewHelpMapping = {
412
423
  [FileType$1.OBS_SEGMENTATIONS_JSON]: DataType$2.OBS_SEGMENTATIONS,
413
424
  [FileType$1.OBS_SETS_CSV]: DataType$2.OBS_SETS,
414
425
  [FileType$1.OBS_SETS_JSON]: DataType$2.OBS_SETS,
426
+ [FileType$1.ANNOTATION_STORY_JSON]: DataType$2.ANNOTATION_STORY,
415
427
  [FileType$1.IMAGE_OME_ZARR]: DataType$2.IMAGE,
416
428
  [FileType$1.OBS_SEGMENTATIONS_OME_ZARR]: DataType$2.OBS_SEGMENTATIONS,
417
429
  [FileType$1.OBS_FEATURE_MATRIX_ANNDATA_ZARR]: DataType$2.OBS_FEATURE_MATRIX,
@@ -534,7 +546,8 @@ const ViewHelpMapping = {
534
546
  [DataType$2.OBS_SET_STATS]: [
535
547
  CoordinationType$1.OBS_TYPE
536
548
  // TODO: should sampleType, obsSetSelection, and/or sampleSetSelection be used here?
537
- ]
549
+ ],
550
+ [DataType$2.ANNOTATION_STORY]: []
538
551
  });
539
552
  ({
540
553
  // For AnnData:
@@ -667,7 +680,9 @@ const ViewHelpMapping = {
667
680
  CoordinationType$1.SPATIAL_TARGET_Z,
668
681
  CoordinationType$1.SPATIAL_AXIS_FIXED,
669
682
  CoordinationType$1.SPATIAL_ORBIT_AXIS,
670
- CoordinationType$1.FEATURE_AGGREGATION_STRATEGY
683
+ CoordinationType$1.FEATURE_AGGREGATION_STRATEGY,
684
+ // Annotation shapes are specific to a view's coordinate system.
685
+ CoordinationType$1.ANNOTATION_SHAPES
671
686
  ];
672
687
  const COMPONENT_COORDINATION_TYPES = {
673
688
  [ViewType$1.NEUROGLANCER]: [
@@ -759,7 +774,14 @@ const COMPONENT_COORDINATION_TYPES = {
759
774
  CoordinationType$1.EMBEDDING_CONTOURS_FILLED,
760
775
  CoordinationType$1.EMBEDDING_CONTOUR_PERCENTILES,
761
776
  CoordinationType$1.CONTOUR_COLOR_ENCODING,
762
- CoordinationType$1.CONTOUR_COLOR
777
+ CoordinationType$1.CONTOUR_COLOR,
778
+ CoordinationType$1.ANNOTATION_STORY,
779
+ CoordinationType$1.ANNOTATION_SHAPES,
780
+ CoordinationType$1.ANNOTATION_FRAME_INDEX,
781
+ CoordinationType$1.ANNOTATION_OVERLAY_VISIBLE,
782
+ CoordinationType$1.ANNOTATION_TRANSITION_DURATION,
783
+ CoordinationType$1.ANNOTATION_EDITABLE,
784
+ CoordinationType$1.ANNOTATION_SEMANTIC_ZOOM
763
785
  ],
764
786
  [ViewType$1.DUAL_SCATTERPLOT]: [
765
787
  CoordinationType$1.DATASET,
@@ -875,7 +897,14 @@ const COMPONENT_COORDINATION_TYPES = {
875
897
  CoordinationType$1.ADDITIONAL_OBS_SETS,
876
898
  CoordinationType$1.MOLECULE_HIGHLIGHT,
877
899
  CoordinationType$1.TOOLTIPS_VISIBLE,
878
- CoordinationType$1.PHOTOMETRIC_INTERPRETATION
900
+ CoordinationType$1.PHOTOMETRIC_INTERPRETATION,
901
+ CoordinationType$1.ANNOTATION_STORY,
902
+ CoordinationType$1.ANNOTATION_SHAPES,
903
+ CoordinationType$1.ANNOTATION_FRAME_INDEX,
904
+ CoordinationType$1.ANNOTATION_OVERLAY_VISIBLE,
905
+ CoordinationType$1.ANNOTATION_TRANSITION_DURATION,
906
+ CoordinationType$1.ANNOTATION_EDITABLE,
907
+ CoordinationType$1.ANNOTATION_SEMANTIC_ZOOM
879
908
  ],
880
909
  [ViewType$1.SPATIAL_BETA]: [
881
910
  CoordinationType$1.META_COORDINATION_SCOPES,
@@ -953,7 +982,27 @@ const COMPONENT_COORDINATION_TYPES = {
953
982
  CoordinationType$1.SPATIAL_CHANNEL_LABELS_ORIENTATION,
954
983
  CoordinationType$1.SPATIAL_CHANNEL_LABEL_SIZE,
955
984
  CoordinationType$1.SPATIAL_CHANNELS_SORT_ORDER,
956
- CoordinationType$1.PHOTOMETRIC_INTERPRETATION
985
+ CoordinationType$1.PHOTOMETRIC_INTERPRETATION,
986
+ CoordinationType$1.ANNOTATION_STORY,
987
+ CoordinationType$1.ANNOTATION_SHAPES,
988
+ CoordinationType$1.ANNOTATION_FRAME_INDEX,
989
+ CoordinationType$1.ANNOTATION_OVERLAY_VISIBLE,
990
+ CoordinationType$1.ANNOTATION_TRANSITION_DURATION,
991
+ CoordinationType$1.ANNOTATION_EDITABLE,
992
+ CoordinationType$1.ANNOTATION_SEMANTIC_ZOOM
993
+ ],
994
+ [ViewType$1.ANNOTATION_CONTROLLER]: [
995
+ CoordinationType$1.DATASET,
996
+ CoordinationType$1.ANNOTATION_STORY,
997
+ CoordinationType$1.ANNOTATION_FRAME_INDEX,
998
+ CoordinationType$1.ANNOTATION_OVERLAY_VISIBLE,
999
+ CoordinationType$1.ANNOTATION_TRANSITION_DURATION,
1000
+ CoordinationType$1.ANNOTATION_EDITABLE,
1001
+ CoordinationType$1.ANNOTATION_SEMANTIC_ZOOM
1002
+ // Note: the annotation controller also needs to read the coordination state
1003
+ // of all other views/coordination types via the full config.
1004
+ // Note: we get/set the CoordinationType.ANNOTATION_SHAPES from the full config,
1005
+ // as this info is view-specific rather than global to the story.
957
1006
  ],
958
1007
  [ViewType$1.HEATMAP]: [
959
1008
  CoordinationType$1.DATASET,
@@ -8679,7 +8728,7 @@ const latestFileDefSchema = z.object({
8679
8728
  options: fileOptionsSchema.optional(),
8680
8729
  requestInit: requestInit.optional(),
8681
8730
  // New: file def can have coordinationValues.
8682
- coordinationValues: z.record(z.string()).optional()
8731
+ coordinationValues: z.record(z.any()).optional()
8683
8732
  });
8684
8733
  const datasetsSchema1_0_13 = z.array(z.object({
8685
8734
  uid: z.string(),
@@ -8710,7 +8759,8 @@ const configSchema1_0_16 = configSchema1_0_13.extend({
8710
8759
  h: z.number().int().optional(),
8711
8760
  // Updates coordinationScopes and coordinationScopesBy
8712
8761
  coordinationScopes: componentCoordinationScopes.optional(),
8713
- coordinationScopesBy: componentCoordinationScopesBy.optional()
8762
+ coordinationScopesBy: componentCoordinationScopesBy.optional(),
8763
+ coordinationValues: z.record(z.any()).optional()
8714
8764
  }))
8715
8765
  });
8716
8766
  const configSchema1_0_17 = configSchema1_0_16.extend({
@@ -11795,6 +11845,124 @@ z.object({
11795
11845
  radius: z.number(),
11796
11846
  opacity: z.number()
11797
11847
  });
11848
+ const shapeBaseObj = z.object({
11849
+ uid: z.string(),
11850
+ text: z.string().optional(),
11851
+ strokeColor: z.array(z.number()).length(3).optional(),
11852
+ strokeWidth: z.number().optional(),
11853
+ // SVG-style dash pattern, e.g. "10 5" or "10 20 30 10". "none" or omitted = solid.
11854
+ strokeDashArray: z.string().optional(),
11855
+ visible: z.boolean().optional()
11856
+ // TODO: define unit modes for x and y axes
11857
+ // ("pixels" vs "normalized" vs "physical" etc.)
11858
+ // TODO: a coordinate system identifier (e.g., "DeckGL" for now,
11859
+ // but a coordinate system from OME-NGFF/SpatialData eventually?)
11860
+ });
11861
+ const rectangleShapeObj = shapeBaseObj.extend({
11862
+ type: z.literal("rectangle"),
11863
+ // OME-XML field names verbatim (upper-left origin)
11864
+ x: z.number(),
11865
+ y: z.number(),
11866
+ width: z.number(),
11867
+ height: z.number(),
11868
+ fillColor: z.array(z.number()).length(3).optional(),
11869
+ fillOpacity: z.number().optional()
11870
+ });
11871
+ const lineShapeObj = shapeBaseObj.extend({
11872
+ type: z.literal("line"),
11873
+ // OME-XML field names verbatim
11874
+ x1: z.number(),
11875
+ y1: z.number(),
11876
+ x2: z.number(),
11877
+ y2: z.number(),
11878
+ markerStart: z.enum(["Arrow", "Tick"]).nullable().optional(),
11879
+ markerEnd: z.enum(["Arrow", "Tick"]).nullable().optional(),
11880
+ textPosition: z.enum(["start", "middle", "end"]).optional(),
11881
+ textBufferPx: z.number().optional()
11882
+ });
11883
+ const ellipseShapeObj = shapeBaseObj.extend({
11884
+ type: z.literal("ellipse"),
11885
+ // OME-XML field names verbatim (centre point + radii)
11886
+ x1: z.number(),
11887
+ y1: z.number(),
11888
+ radiusX: z.number(),
11889
+ radiusY: z.number(),
11890
+ fillColor: z.array(z.number()).length(3).optional(),
11891
+ fillOpacity: z.number().optional()
11892
+ });
11893
+ const polygonShapeObj = shapeBaseObj.extend({
11894
+ type: z.literal("polygon"),
11895
+ // OME-XML: array of [x, y] coordinate pairs, closed automatically
11896
+ points: z.array(z.tuple([z.number(), z.number()])),
11897
+ fillColor: z.array(z.number()).length(3).optional(),
11898
+ fillOpacity: z.number().optional()
11899
+ });
11900
+ const polylineShapeObj = shapeBaseObj.extend({
11901
+ type: z.literal("polyline"),
11902
+ // OME-XML: array of [x, y] coordinate pairs, open (not closed)
11903
+ points: z.array(z.tuple([z.number(), z.number()])),
11904
+ markerStart: z.enum(["Arrow", "Tick"]).nullable().optional(),
11905
+ markerEnd: z.enum(["Arrow", "Tick"]).nullable().optional()
11906
+ });
11907
+ const annotationShapeObj = z.discriminatedUnion("type", [
11908
+ rectangleShapeObj,
11909
+ lineShapeObj,
11910
+ ellipseShapeObj,
11911
+ polygonShapeObj,
11912
+ polylineShapeObj
11913
+ ]);
11914
+ z.array(annotationShapeObj);
11915
+ const annotationFrameObj = z.object({
11916
+ uid: z.string(),
11917
+ title: z.string().optional(),
11918
+ description: z.string().optional(),
11919
+ descriptionType: z.enum(["text", "markdown"]).optional(),
11920
+ // A frame can define per-view: coordinationValues
11921
+ // (if the view's state should not be coordinated with other views)
11922
+ // or per-view coordinationScopes/coordinationScopesBy
11923
+ // (if at this frame, the view should be coordinated with other views).
11924
+ // We can also define a frame-level coordination space,
11925
+ // which will be merged with the global coordination space.
11926
+ // TODO: datasets
11927
+ // TODO: coordinationSpace
11928
+ layout: z.array(
11929
+ // Note: for now, we will infer view objects which have a uid
11930
+ // that is present in the global layout, and do NOT have x/y/w/h defined,
11931
+ // to mean that in this frame, the layout and set of views is unchanged
11932
+ // from the global layout.
11933
+ // This will allow each frame to omit the full list of views from the layout,
11934
+ // and only specify the views whose coordination state is changing in this frame.
11935
+ // If a frame includes a view with a uid that is not present
11936
+ // in the global layout, then that view will be added to the layout at this frame.
11937
+ // This frame should also define x/y/w/h for all views,
11938
+ // since it will otherwise be ambiguous how to layout the updated set of views
11939
+ // for this frame.
11940
+ z.object({
11941
+ // The view UID. If this view uid is not present in the global layout,
11942
+ // it will be added to the layout at this frame,
11943
+ // and will need to have its coordination values initialized to their defaults
11944
+ // for any that were not fully specified.
11945
+ uid: z.string(),
11946
+ coordinationValues: z.record(
11947
+ z.string(),
11948
+ // The coordination type name, e.g. "embeddingType"
11949
+ z.any()
11950
+ ).optional()
11951
+ // TODO: support coordinationScopes/coordinationScopesBy for per-view coordination
11952
+ // at this frame.
11953
+ // TODO: view x/y/w/h once we support view layout changes per-frame.
11954
+ })
11955
+ ).optional()
11956
+ });
11957
+ z.object({
11958
+ // TODO: schema version for the story schema?
11959
+ // Same as current Vitessce schema version?
11960
+ uid: z.string(),
11961
+ title: z.string().optional(),
11962
+ description: z.string().optional(),
11963
+ descriptionType: z.enum(["text", "markdown"]).optional(),
11964
+ frames: z.array(annotationFrameObj)
11965
+ });
11798
11966
  const SETS_DATATYPE_OBS = "obs";
11799
11967
  const HIERARCHICAL_SCHEMAS = {
11800
11968
  latestVersion: "0.1.3"
@@ -21745,7 +21913,7 @@ var values$5 = Object.values || function(obj) {
21745
21913
  return obj[k];
21746
21914
  });
21747
21915
  };
21748
- var defaultProps$P = {
21916
+ var defaultProps$Q = {
21749
21917
  component: "div",
21750
21918
  childFactory: function childFactory(child) {
21751
21919
  return child;
@@ -21872,7 +22040,7 @@ TransitionGroup.propTypes = {
21872
22040
  */
21873
22041
  childFactory: _pt.func
21874
22042
  };
21875
- TransitionGroup.defaultProps = defaultProps$P;
22043
+ TransitionGroup.defaultProps = defaultProps$Q;
21876
22044
  const UNINITIALIZED = {};
21877
22045
  function useLazyRef(init2, initArg) {
21878
22046
  const ref2 = React.useRef(UNINITIALIZED);
@@ -82094,6 +82262,12 @@ function multiply$3(out, a2, b2) {
82094
82262
  out[2] = a2[2] * b2[2];
82095
82263
  return out;
82096
82264
  }
82265
+ function distance$3(a2, b2) {
82266
+ var x2 = b2[0] - a2[0];
82267
+ var y2 = b2[1] - a2[1];
82268
+ var z2 = b2[2] - a2[2];
82269
+ return Math.hypot(x2, y2, z2);
82270
+ }
82097
82271
  function squaredLength$2(a2) {
82098
82272
  var x2 = a2[0];
82099
82273
  var y2 = a2[1];
@@ -82217,6 +82391,7 @@ function angle$3(a2, b2) {
82217
82391
  }
82218
82392
  var sub = subtract;
82219
82393
  var mul = multiply$3;
82394
+ var dist$1 = distance$3;
82220
82395
  var len = length$5;
82221
82396
  var sqrLen = squaredLength$2;
82222
82397
  (function() {
@@ -96687,7 +96862,7 @@ function noop$7() {
96687
96862
  const getCursor = ({
96688
96863
  isDragging
96689
96864
  }) => isDragging ? "grabbing" : "grab";
96690
- const defaultProps$O = {
96865
+ const defaultProps$P = {
96691
96866
  id: "",
96692
96867
  width: "100%",
96693
96868
  height: "100%",
@@ -96818,7 +96993,7 @@ class Deck {
96818
96993
  this._lastPointerDownInfo = pickedInfo.result[0] || pickedInfo.emptyInfo;
96819
96994
  });
96820
96995
  this.props = {
96821
- ...defaultProps$O,
96996
+ ...defaultProps$P,
96822
96997
  ...props
96823
96998
  };
96824
96999
  props = this.props;
@@ -97320,7 +97495,7 @@ class Deck {
97320
97495
  metrics.gpuMemory = memoryStats.get("GPU Memory").count;
97321
97496
  }
97322
97497
  }
97323
- _defineProperty$3(Deck, "defaultProps", defaultProps$O);
97498
+ _defineProperty$3(Deck, "defaultProps", defaultProps$P);
97324
97499
  _defineProperty$3(Deck, "VERSION", deckGlobal.VERSION);
97325
97500
  class ShaderAttribute {
97326
97501
  constructor(dataColumn, opts2) {
@@ -100075,7 +100250,7 @@ const areViewportsEqual = memoize$2(({
100075
100250
  return oldViewport.equals(viewport2);
100076
100251
  });
100077
100252
  let pickingColorCache = new Uint8ClampedArray(0);
100078
- const defaultProps$N = {
100253
+ const defaultProps$O = {
100079
100254
  data: {
100080
100255
  type: "data",
100081
100256
  value: EMPTY_ARRAY$1,
@@ -100950,7 +101125,7 @@ class Layer extends Component {
100950
101125
  this.setNeedsUpdate();
100951
101126
  }
100952
101127
  }
100953
- _defineProperty$3(Layer, "defaultProps", defaultProps$N);
101128
+ _defineProperty$3(Layer, "defaultProps", defaultProps$O);
100954
101129
  _defineProperty$3(Layer, "layerName", "Layer");
100955
101130
  const TRACE_RENDER_LAYERS = "compositeLayer.renderLayers";
100956
101131
  class CompositeLayer extends Layer {
@@ -101986,6 +102161,124 @@ const esm$5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
101986
102161
  project32,
101987
102162
  shadow
101988
102163
  }, Symbol.toStringTag, { value: "Module" }));
102164
+ const dashShaders = {
102165
+ inject: {
102166
+ "vs:#decl": "\nattribute vec2 instanceDashArrays;\nattribute float instanceDashOffsets;\nvarying vec2 vDashArray;\nvarying float vDashOffset;\n",
102167
+ "vs:#main-end": "\nvDashArray = instanceDashArrays;\nvDashOffset = instanceDashOffsets / width.x;\n",
102168
+ "fs:#decl": "\nuniform float dashAlignMode;\nuniform float capType;\nuniform bool dashGapPickable;\nvarying vec2 vDashArray;\nvarying float vDashOffset;\n\nfloat round(float x) {\n return floor(x + 0.5);\n}\n",
102169
+ "fs:#main-start": "\n float solidLength = vDashArray.x;\n float gapLength = vDashArray.y;\n float unitLength = solidLength + gapLength;\n\n float offset;\n\n if (unitLength > 0.0) {\n if (dashAlignMode == 0.0) {\n offset = vDashOffset;\n } else {\n unitLength = vPathLength / round(vPathLength / unitLength);\n offset = solidLength / 2.0;\n }\n\n float unitOffset = mod(clamp(vPathPosition.y, 0.0, vPathLength) + offset, unitLength);\n\n if (gapLength > 0.0 && unitOffset > solidLength) {\n if (capType <= 0.5) {\n if (!(dashGapPickable && picking_uActive)) {\n discard;\n }\n } else {\n // caps are rounded, test the distance to solid ends\n float distToEnd = length(vec2(\n min(unitOffset - solidLength, unitLength - unitOffset),\n vPathPosition.x\n ));\n if (distToEnd > 1.0) {\n if (!(dashGapPickable && picking_uActive)) {\n discard;\n }\n }\n }\n }\n }\n"
102170
+ }
102171
+ };
102172
+ const offsetShaders = {
102173
+ inject: {
102174
+ "vs:#decl": "\nattribute float instanceOffsets;\n",
102175
+ "vs:DECKGL_FILTER_SIZE": "\n float offsetWidth = abs(instanceOffsets * 2.0) + 1.0;\n size *= offsetWidth;\n",
102176
+ "vCornerOffset = offsetVec;": "\n float offsetWidth = abs(instanceOffsets * 2.0) + 1.0;\n vec2 offsetCenter = -instanceOffsets * (isCap ? perp : miterVec * miterSize) * 2.0;\n vCornerOffset = vCornerOffset * offsetWidth - offsetCenter;\n",
102177
+ "fs:#main-start": "\n float isInside;\n isInside = step(-1.0, vPathPosition.x) * step(vPathPosition.x, 1.0);\n if (isInside == 0.0) {\n discard;\n }\n"
102178
+ }
102179
+ };
102180
+ const defaultProps$N = {
102181
+ getDashArray: {
102182
+ type: "accessor",
102183
+ value: [0, 0]
102184
+ },
102185
+ getOffset: {
102186
+ type: "accessor",
102187
+ value: 0
102188
+ },
102189
+ dashJustified: false,
102190
+ dashGapPickable: false
102191
+ };
102192
+ class PathStyleExtension extends LayerExtension {
102193
+ constructor({
102194
+ dash = false,
102195
+ offset: offset2 = false,
102196
+ highPrecisionDash = false
102197
+ } = {}) {
102198
+ super({
102199
+ dash: dash || highPrecisionDash,
102200
+ offset: offset2,
102201
+ highPrecisionDash
102202
+ });
102203
+ }
102204
+ isEnabled(layer) {
102205
+ return "pathTesselator" in layer.state;
102206
+ }
102207
+ getShaders(extension2) {
102208
+ if (!extension2.isEnabled(this)) {
102209
+ return null;
102210
+ }
102211
+ let result = {};
102212
+ if (extension2.opts.dash) {
102213
+ result = mergeShaders(result, dashShaders);
102214
+ }
102215
+ if (extension2.opts.offset) {
102216
+ result = mergeShaders(result, offsetShaders);
102217
+ }
102218
+ return result;
102219
+ }
102220
+ initializeState(context2, extension2) {
102221
+ const attributeManager = this.getAttributeManager();
102222
+ if (!attributeManager || !extension2.isEnabled(this)) {
102223
+ return;
102224
+ }
102225
+ if (extension2.opts.dash) {
102226
+ attributeManager.addInstanced({
102227
+ instanceDashArrays: {
102228
+ size: 2,
102229
+ accessor: "getDashArray"
102230
+ }
102231
+ });
102232
+ }
102233
+ if (extension2.opts.highPrecisionDash) {
102234
+ attributeManager.addInstanced({
102235
+ instanceDashOffsets: {
102236
+ size: 1,
102237
+ accessor: "getPath",
102238
+ transform: extension2.getDashOffsets.bind(this)
102239
+ }
102240
+ });
102241
+ }
102242
+ if (extension2.opts.offset) {
102243
+ attributeManager.addInstanced({
102244
+ instanceOffsets: {
102245
+ size: 1,
102246
+ accessor: "getOffset"
102247
+ }
102248
+ });
102249
+ }
102250
+ }
102251
+ updateState(params2, extension2) {
102252
+ if (!extension2.isEnabled(this)) {
102253
+ return;
102254
+ }
102255
+ const uniforms = {};
102256
+ if (extension2.opts.dash) {
102257
+ uniforms.dashAlignMode = this.props.dashJustified ? 1 : 0;
102258
+ uniforms.dashGapPickable = Boolean(this.props.dashGapPickable);
102259
+ }
102260
+ this.state.model.setUniforms(uniforms);
102261
+ }
102262
+ getDashOffsets(path2) {
102263
+ const result = [0];
102264
+ const positionSize = this.props.positionFormat === "XY" ? 2 : 3;
102265
+ const isNested2 = Array.isArray(path2[0]);
102266
+ const geometrySize = isNested2 ? path2.length : path2.length / positionSize;
102267
+ let p;
102268
+ let prevP;
102269
+ for (let i2 = 0; i2 < geometrySize - 1; i2++) {
102270
+ p = isNested2 ? path2[i2] : path2.slice(i2 * positionSize, i2 * positionSize + positionSize);
102271
+ p = this.projectPosition(p);
102272
+ if (i2 > 0) {
102273
+ result[i2] = result[i2 - 1] + dist$1(prevP, p);
102274
+ }
102275
+ prevP = p;
102276
+ }
102277
+ return result;
102278
+ }
102279
+ }
102280
+ _defineProperty$3(PathStyleExtension, "defaultProps", defaultProps$N);
102281
+ _defineProperty$3(PathStyleExtension, "extensionName", "PathStyleExtension");
101989
102282
  const defaultProps$M = {
101990
102283
  clipBounds: [0, 0, 1, 1]
101991
102284
  };
@@ -174341,22 +174634,22 @@ function addDecoder(cases2, importFn) {
174341
174634
  }
174342
174635
  cases2.forEach((c2) => registry$1.set(c2, importFn));
174343
174636
  }
174344
- addDecoder([void 0, 1], () => import("./raw-Vb44MxD9.js").then((m2) => m2.default));
174345
- addDecoder(5, () => import("./lzw-CKBozwxj.js").then((m2) => m2.default));
174637
+ addDecoder([void 0, 1], () => import("./raw-Dg8kCOnj.js").then((m2) => m2.default));
174638
+ addDecoder(5, () => import("./lzw-DkbMJx1b.js").then((m2) => m2.default));
174346
174639
  addDecoder(6, () => {
174347
174640
  throw new Error("old style JPEG compression is not supported.");
174348
174641
  });
174349
- addDecoder(7, () => import("./jpeg-KhuOxw4Z.js").then((m2) => m2.default));
174350
- addDecoder([8, 32946], () => import("./deflate--aCxt3Uk.js").then((m2) => m2.default));
174351
- addDecoder(32773, () => import("./packbits-DBXx1JcY.js").then((m2) => m2.default));
174642
+ addDecoder(7, () => import("./jpeg-DhNGUcND.js").then((m2) => m2.default));
174643
+ addDecoder([8, 32946], () => import("./deflate-BG2HUqgg.js").then((m2) => m2.default));
174644
+ addDecoder(32773, () => import("./packbits-8pX5TxcK.js").then((m2) => m2.default));
174352
174645
  addDecoder(
174353
174646
  34887,
174354
- () => import("./lerc-CElkXFuI.js").then(async (m2) => {
174647
+ () => import("./lerc-CeaVG0Hd.js").then(async (m2) => {
174355
174648
  await m2.zstd.init();
174356
174649
  return m2;
174357
174650
  }).then((m2) => m2.default)
174358
174651
  );
174359
- addDecoder(50001, () => import("./webimage-Df6sOKlo.js").then((m2) => m2.default));
174652
+ addDecoder(50001, () => import("./webimage-DqIQm4cj.js").then((m2) => m2.default));
174360
174653
  function decodeRowAcc(row, stride) {
174361
174654
  let length2 = row.length - stride;
174362
174655
  let offset2 = 0;
@@ -181559,7 +181852,7 @@ function padColors({ colors: colors2, channelsVisible }) {
181559
181852
  ).reduce((acc, val) => acc.concat(val), []);
181560
181853
  return paddedColors;
181561
181854
  }
181562
- const defaultProps$3$1 = {
181855
+ const defaultProps$3$2 = {
181563
181856
  colors: { type: "array", value: null, compare: true },
181564
181857
  opacity: { type: "number", value: 1, compare: true },
181565
181858
  transparentColor: { type: "array", value: null, compare: true },
@@ -181576,9 +181869,9 @@ const ColorPaletteExtension = class extends LayerExtension {
181576
181869
  const {
181577
181870
  colors: colors2,
181578
181871
  channelsVisible,
181579
- opacity: opacity2 = defaultProps$3$1.opacity.value,
181580
- transparentColor = defaultProps$3$1.transparentColor.value,
181581
- useTransparentColor = defaultProps$3$1.useTransparentColor.value
181872
+ opacity: opacity2 = defaultProps$3$2.opacity.value,
181873
+ transparentColor = defaultProps$3$2.transparentColor.value,
181874
+ useTransparentColor = defaultProps$3$2.useTransparentColor.value
181582
181875
  } = this.props;
181583
181876
  const paddedColors = padColors({
181584
181877
  channelsVisible: channelsVisible || this.selections.map(() => true),
@@ -181594,7 +181887,7 @@ const ColorPaletteExtension = class extends LayerExtension {
181594
181887
  }
181595
181888
  };
181596
181889
  ColorPaletteExtension.extensionName = "ColorPaletteExtension";
181597
- ColorPaletteExtension.defaultProps = defaultProps$3$1;
181890
+ ColorPaletteExtension.defaultProps = defaultProps$3$2;
181598
181891
  const fs$4 = `// lens bounds for ellipse
181599
181892
  uniform float majorLensAxis;
181600
181893
  uniform float minorLensAxis;
@@ -182889,7 +183182,7 @@ const MultiscaleImageLayer = class extends CompositeLayer {
182889
183182
  };
182890
183183
  MultiscaleImageLayer.layerName = "MultiscaleImageLayer";
182891
183184
  MultiscaleImageLayer.defaultProps = defaultProps$4;
182892
- const defaultProps$3 = {
183185
+ const defaultProps$3$1 = {
182893
183186
  pickable: { type: "boolean", value: true, compare: true },
182894
183187
  loader: {
182895
183188
  type: "object",
@@ -182976,7 +183269,7 @@ const OverviewLayer = class extends CompositeLayer {
182976
183269
  }
182977
183270
  };
182978
183271
  OverviewLayer.layerName = "OverviewLayer";
182979
- OverviewLayer.defaultProps = defaultProps$3;
183272
+ OverviewLayer.defaultProps = defaultProps$3$1;
182980
183273
  function getPosition(boundingBox, position2, length2) {
182981
183274
  const viewWidth2 = boundingBox[2][0] - boundingBox[0][0];
182982
183275
  const viewHeight2 = boundingBox[2][1] - boundingBox[0][1];
@@ -183964,7 +184257,7 @@ function padWithDefault$1(arr, defaultValue2, padWidth) {
183964
184257
  }
183965
184258
  return newArr;
183966
184259
  }
183967
- const defaultProps$2 = {
184260
+ const defaultProps$3 = {
183968
184261
  hoveredCell: { type: "number", value: null, compare: true },
183969
184262
  // We do not want to deep-compare cellColorData,
183970
184263
  // as it is potentially a TypedArray with millions of elements.
@@ -184081,7 +184374,7 @@ let BitmaskLayer$1 = class BitmaskLayer extends XRLayer {
184081
184374
  }
184082
184375
  };
184083
184376
  BitmaskLayer$1.layerName = "BitmaskLayer";
184084
- BitmaskLayer$1.defaultProps = defaultProps$2;
184377
+ BitmaskLayer$1.defaultProps = defaultProps$3;
184085
184378
  const vs = `
184086
184379
  #define SHADER_NAME bitmask-layer-vertex-shader
184087
184380
 
@@ -184423,7 +184716,7 @@ function isEqualShallow(prevArr, nextArr) {
184423
184716
  }
184424
184717
  return prevArr === nextArr;
184425
184718
  }
184426
- const defaultProps$1 = {
184719
+ const defaultProps$2 = {
184427
184720
  channelStrokeWidths: { type: "array", value: null, compare: true },
184428
184721
  channelsFilled: { type: "array", value: null, compare: true },
184429
184722
  channelOpacities: { type: "array", value: null, compare: true },
@@ -184687,7 +184980,7 @@ class BitmaskLayer2 extends XRLayer {
184687
184980
  }
184688
184981
  }
184689
184982
  BitmaskLayer2.layerName = "BitmaskLayer";
184690
- BitmaskLayer2.defaultProps = defaultProps$1;
184983
+ BitmaskLayer2.defaultProps = defaultProps$2;
184691
184984
  var isNumeric$1 = /^-?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i, mathceil = Math.ceil, mathfloor = Math.floor, bignumberError = "[BigNumber Error] ", tooManyDigits = bignumberError + "Number primitive has more than 15 significant digits: ", BASE = 1e14, LOG_BASE = 14, MAX_SAFE_INTEGER$2 = 9007199254740991, POWS_TEN = [1, 10, 100, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13], SQRT_BASE = 1e7, MAX$1 = 1e9;
184692
184985
  function clone$1(configObject) {
184693
184986
  var div, convertBase, parseNumeric, P = BigNumber2.prototype = { constructor: BigNumber2, toString: null, valueOf: null }, ONE = new BigNumber2(1), DECIMAL_PLACES = 20, ROUNDING_MODE = 4, TO_EXP_NEG = -7, TO_EXP_POS = 21, MIN_EXP = -1e7, MAX_EXP = 1e7, CRYPTO = false, MODULO_MODE = 1, POW_PRECISION = 0, FORMAT = {
@@ -187630,7 +187923,7 @@ function ringArea(coords) {
187630
187923
  return total * FACTOR;
187631
187924
  }
187632
187925
  const DEFAULT_THRESHOLD = 1;
187633
- const defaultProps = {
187926
+ const defaultProps$1 = {
187634
187927
  // for text
187635
187928
  obsSetPath: { type: "object" },
187636
187929
  sampleSetPath: { type: "object" },
@@ -187804,7 +188097,1077 @@ class ContourLayerWithText extends ContourLayer {
187804
188097
  }
187805
188098
  }
187806
188099
  ContourLayerWithText.layerName = "ContourLayerWithText";
187807
- ContourLayerWithText.defaultProps = defaultProps;
188100
+ ContourLayerWithText.defaultProps = defaultProps$1;
188101
+ const ARROW_SCREEN_PX = 14;
188102
+ const LABEL_FONT_FAMILY = '"Roboto", "Helvetica", "Arial", sans-serif';
188103
+ const LABEL_FONT_WEIGHT = "bold";
188104
+ const LABEL_FONT_SIZE = 14;
188105
+ const RATIO_CLUSTER = 0.1;
188106
+ const RATIO_DOT = 0.3;
188107
+ const RATIO_GEO_FADE = 0.55;
188108
+ const RATIO_TIPS = 0.7;
188109
+ const RATIO_LABEL = 0.55;
188110
+ const RATIO_LABEL_FULL = 0.75;
188111
+ function textBgColor(strokeColor) {
188112
+ const [r3, g2, b2] = strokeColor;
188113
+ const lum = (0.299 * r3 + 0.587 * g2 + 0.114 * b2) / 255;
188114
+ return lum > 0.45 ? [20, 20, 20, 210] : [245, 245, 245, 210];
188115
+ }
188116
+ function parseDashArray(strokeDashArray) {
188117
+ if (!strokeDashArray || strokeDashArray === "none")
188118
+ return null;
188119
+ const nums = strokeDashArray.trim().split(/\s+/).map(Number).filter((n2) => !Number.isNaN(n2) && n2 >= 0);
188120
+ return nums.length >= 2 ? [nums[0], nums[1]] : null;
188121
+ }
188122
+ function computeEllipsePolygon(cx, cy, rx, ry, numSegments = 64) {
188123
+ const pts = [];
188124
+ for (let i2 = 0; i2 < numSegments; i2++) {
188125
+ const angle2 = 2 * Math.PI * i2 / numSegments;
188126
+ pts.push([cx + rx * Math.cos(angle2), cy + ry * Math.sin(angle2)]);
188127
+ }
188128
+ return pts;
188129
+ }
188130
+ function createPreviewLayer(inProgress, hoverCoord, strokeColor = [255, 255, 0]) {
188131
+ if (!inProgress || inProgress.vertices.length === 0)
188132
+ return null;
188133
+ const { type: type2, vertices } = inProgress;
188134
+ const base2 = { coordinateSystem: COORDINATE_SYSTEM.CARTESIAN };
188135
+ const previewColor = [...strokeColor, 160];
188136
+ const cursor2 = hoverCoord ?? vertices[vertices.length - 1];
188137
+ if (type2 === "rectangle" && vertices.length === 1) {
188138
+ const [ax, ay] = vertices[0];
188139
+ const [bx, by] = cursor2;
188140
+ const path2 = [[ax, ay], [bx, ay], [bx, by], [ax, by], [ax, ay]];
188141
+ return new PathLayer({
188142
+ ...base2,
188143
+ id: "annotation-preview-rectangle",
188144
+ data: [{ path: path2 }],
188145
+ getPath: (d) => d.path,
188146
+ getColor: previewColor,
188147
+ getWidth: 1,
188148
+ widthUnits: "pixels",
188149
+ extensions: [new PathStyleExtension({ dash: true })],
188150
+ getDashArray: [8, 4]
188151
+ });
188152
+ }
188153
+ if (type2 === "line" && vertices.length === 1) {
188154
+ const path2 = [vertices[0], cursor2];
188155
+ return new PathLayer({
188156
+ ...base2,
188157
+ id: "annotation-preview-line",
188158
+ data: [{ path: path2 }],
188159
+ getPath: (d) => d.path,
188160
+ getColor: previewColor,
188161
+ getWidth: 1,
188162
+ widthUnits: "pixels",
188163
+ extensions: [new PathStyleExtension({ dash: true })],
188164
+ getDashArray: [8, 4]
188165
+ });
188166
+ }
188167
+ if (type2 === "ellipse" && vertices.length === 1) {
188168
+ const [cx, cy] = vertices[0];
188169
+ const [ex, ey] = cursor2;
188170
+ const rx = Math.abs(ex - cx);
188171
+ const ry = Math.abs(ey - cy);
188172
+ if (rx < 1 && ry < 1)
188173
+ return null;
188174
+ const pts = computeEllipsePolygon(cx, cy, Math.max(rx, 1), Math.max(ry, 1));
188175
+ const path2 = [...pts, pts[0]];
188176
+ return new PathLayer({
188177
+ ...base2,
188178
+ id: "annotation-preview-ellipse",
188179
+ data: [{ path: path2 }],
188180
+ getPath: (d) => d.path,
188181
+ getColor: previewColor,
188182
+ getWidth: 1,
188183
+ widthUnits: "pixels",
188184
+ extensions: [new PathStyleExtension({ dash: true })],
188185
+ getDashArray: [8, 4]
188186
+ });
188187
+ }
188188
+ if ((type2 === "polygon" || type2 === "polyline") && vertices.length >= 1) {
188189
+ const path2 = [...vertices, cursor2];
188190
+ if (type2 === "polygon" && vertices.length >= 2)
188191
+ path2.push(vertices[0]);
188192
+ return new PathLayer({
188193
+ ...base2,
188194
+ id: `annotation-preview-${type2}`,
188195
+ data: [{ path: path2 }],
188196
+ getPath: (d) => d.path,
188197
+ getColor: previewColor,
188198
+ getWidth: 1,
188199
+ widthUnits: "pixels",
188200
+ jointRounded: true,
188201
+ capRounded: true,
188202
+ extensions: [new PathStyleExtension({ dash: true })],
188203
+ getDashArray: [8, 4]
188204
+ });
188205
+ }
188206
+ return null;
188207
+ }
188208
+ function computeTickCap(tipX, tipY, dirX, dirY, dataSize) {
188209
+ const len2 = Math.sqrt(dirX * dirX + dirY * dirY);
188210
+ if (len2 === 0)
188211
+ return null;
188212
+ const ux = dirX / len2;
188213
+ const uy = dirY / len2;
188214
+ const px2 = -uy;
188215
+ const py2 = ux;
188216
+ const half = dataSize * 0.55;
188217
+ return [
188218
+ [tipX + px2 * half, tipY + py2 * half],
188219
+ [tipX - px2 * half, tipY - py2 * half]
188220
+ ];
188221
+ }
188222
+ function computeArrowhead(tipX, tipY, dirX, dirY, dataSize) {
188223
+ const len2 = Math.sqrt(dirX * dirX + dirY * dirY);
188224
+ if (len2 === 0)
188225
+ return null;
188226
+ const ux = dirX / len2;
188227
+ const uy = dirY / len2;
188228
+ const px2 = -uy;
188229
+ const py2 = ux;
188230
+ const halfBase = dataSize * 0.4;
188231
+ const bx = tipX - ux * dataSize;
188232
+ const by = tipY - uy * dataSize;
188233
+ return [
188234
+ [tipX, tipY],
188235
+ [bx + px2 * halfBase, by + py2 * halfBase],
188236
+ [bx - px2 * halfBase, by - py2 * halfBase]
188237
+ ];
188238
+ }
188239
+ function fmtCommas(n2) {
188240
+ return Math.round(n2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
188241
+ }
188242
+ function getMeasurementLabel(shape2, physicalPixelSize, viewType = ViewType$1.SPATIAL) {
188243
+ const pps = physicalPixelSize;
188244
+ const px2 = pps?.x ?? 1;
188245
+ const py2 = pps?.y ?? 1;
188246
+ const unit2 = pps ? pps.unit ?? "" : null;
188247
+ const hasPhys = !!pps;
188248
+ const isEmbedding = viewType === ViewType$1.SCATTERPLOT;
188249
+ const fmtDist = (d) => {
188250
+ if (hasPhys)
188251
+ return `${d.toFixed(2)} ${unit2}`;
188252
+ if (isEmbedding)
188253
+ return d.toFixed(2);
188254
+ return `${fmtCommas(d)} px`;
188255
+ };
188256
+ const fmtArea = (a2) => {
188257
+ if (hasPhys)
188258
+ return `${a2.toFixed(2)} ${unit2}²`;
188259
+ if (isEmbedding)
188260
+ return a2.toFixed(2);
188261
+ return `${fmtCommas(a2)} px²`;
188262
+ };
188263
+ if (shape2.type === "line") {
188264
+ const dx = (shape2.x2 ?? 0) - (shape2.x1 ?? 0);
188265
+ const dy = (shape2.y2 ?? 0) - (shape2.y1 ?? 0);
188266
+ const dist2 = hasPhys ? Math.sqrt((dx * px2) ** 2 + (dy * py2) ** 2) : Math.sqrt(dx * dx + dy * dy);
188267
+ return fmtDist(dist2);
188268
+ }
188269
+ if (shape2.type === "rectangle") {
188270
+ return fmtArea(hasPhys ? Math.abs(shape2.width ?? 0) * px2 * Math.abs(shape2.height ?? 0) * py2 : Math.abs((shape2.width ?? 0) * (shape2.height ?? 0)));
188271
+ }
188272
+ if (shape2.type === "ellipse") {
188273
+ const rx = Math.abs(shape2.radiusX ?? 0);
188274
+ const ry = Math.abs(shape2.radiusY ?? 0);
188275
+ return fmtArea(hasPhys ? Math.PI * rx * px2 * ry * py2 : Math.PI * rx * ry);
188276
+ }
188277
+ if (shape2.type === "polygon" && shape2.points?.length >= 3) {
188278
+ const pts = shape2.points;
188279
+ let shoelace = 0;
188280
+ for (let i2 = 0; i2 < pts.length; i2++) {
188281
+ const [ax, ay] = pts[i2];
188282
+ const [bx, by] = pts[(i2 + 1) % pts.length];
188283
+ shoelace += (hasPhys ? ax * px2 : ax) * (hasPhys ? by * py2 : by) - (hasPhys ? bx * px2 : bx) * (hasPhys ? ay * py2 : ay);
188284
+ }
188285
+ return fmtArea(Math.abs(shoelace / 2));
188286
+ }
188287
+ if (shape2.type === "polyline" && shape2.points?.length >= 2) {
188288
+ let total = 0;
188289
+ for (let i2 = 0; i2 < shape2.points.length - 1; i2++) {
188290
+ const [ax, ay] = shape2.points[i2];
188291
+ const [bx, by] = shape2.points[i2 + 1];
188292
+ total += hasPhys ? Math.sqrt(((bx - ax) * px2) ** 2 + ((by - ay) * py2) ** 2) : Math.sqrt((bx - ax) ** 2 + (by - ay) ** 2);
188293
+ }
188294
+ return fmtDist(total);
188295
+ }
188296
+ return null;
188297
+ }
188298
+ function pickBelowPerp(ux, uy) {
188299
+ const rp = [uy, -ux];
188300
+ const lp = [-uy, ux];
188301
+ return rp[1] > lp[1] || rp[1] === lp[1] && rp[0] > lp[0] ? rp : lp;
188302
+ }
188303
+ function readableAngle(deg) {
188304
+ let a2 = deg;
188305
+ if (a2 > 90)
188306
+ a2 -= 180;
188307
+ if (a2 < -90)
188308
+ a2 += 180;
188309
+ return a2;
188310
+ }
188311
+ function getShapeCentroid(shape2) {
188312
+ if (shape2.type === "rectangle") {
188313
+ return [(shape2.x ?? 0) + (shape2.width ?? 0) / 2, (shape2.y ?? 0) + (shape2.height ?? 0) / 2];
188314
+ }
188315
+ if (shape2.type === "line") {
188316
+ return [((shape2.x1 ?? 0) + (shape2.x2 ?? 0)) / 2, ((shape2.y1 ?? 0) + (shape2.y2 ?? 0)) / 2];
188317
+ }
188318
+ if (shape2.type === "ellipse") {
188319
+ return [shape2.x1 ?? 0, shape2.y1 ?? 0];
188320
+ }
188321
+ if ((shape2.type === "polygon" || shape2.type === "polyline") && shape2.points?.length >= 1) {
188322
+ const mx = shape2.points.reduce((s2, p) => s2 + p[0], 0) / shape2.points.length;
188323
+ const my = shape2.points.reduce((s2, p) => s2 + p[1], 0) / shape2.points.length;
188324
+ return [mx, my];
188325
+ }
188326
+ return [0, 0];
188327
+ }
188328
+ function computeClusters(shapeList, zoom2, cellPx = 50) {
188329
+ const cellSize = cellPx / 2 ** Math.floor(zoom2);
188330
+ const cells = /* @__PURE__ */ new Map();
188331
+ shapeList.forEach(({ shape: shape2 }) => {
188332
+ const [cx, cy] = getShapeCentroid(shape2);
188333
+ const key2 = `${Math.floor(cx / cellSize)},${Math.floor(cy / cellSize)}`;
188334
+ if (!cells.has(key2)) {
188335
+ cells.set(key2, { sumX: 0, sumY: 0, count: 0, color: shape2.strokeColor ?? [255, 255, 255] });
188336
+ }
188337
+ const c2 = cells.get(key2);
188338
+ c2.sumX += cx;
188339
+ c2.sumY += cy;
188340
+ c2.count++;
188341
+ });
188342
+ return Array.from(cells.values()).map((c2) => ({
188343
+ position: [c2.sumX / c2.count, c2.sumY / c2.count, 0],
188344
+ count: c2.count,
188345
+ color: c2.color
188346
+ }));
188347
+ }
188348
+ function createAnnotationLayers(shapes = [], zoom2 = 0, selectedShapeUid = null, physicalPixelSize = null, authoredZoom = null, semanticZoom = true, viewType = ViewType$1.SPATIAL) {
188349
+ const selectionLayers = [];
188350
+ if (selectedShapeUid) {
188351
+ const sel = shapes.find((s2) => s2.uid === selectedShapeUid);
188352
+ if (sel) {
188353
+ const { uid: sUid, type: sType, strokeWidth: sStrokeWidth = 3 } = sel;
188354
+ const haloColor = [255, 220, 50, 70];
188355
+ const haloWidth = Math.max(sStrokeWidth + 8, 10);
188356
+ const base2 = { coordinateSystem: COORDINATE_SYSTEM.CARTESIAN };
188357
+ if (sType === "rectangle") {
188358
+ const { x: x2, y: y2, width: width2, height: height2 } = sel;
188359
+ selectionLayers.push(new PathLayer({
188360
+ ...base2,
188361
+ id: `annotation-select-${sUid}`,
188362
+ data: [{
188363
+ path: [
188364
+ [x2, y2],
188365
+ [x2 + width2, y2],
188366
+ [x2 + width2, y2 + height2],
188367
+ [x2, y2 + height2],
188368
+ [x2, y2]
188369
+ ]
188370
+ }],
188371
+ getPath: (d) => d.path,
188372
+ getColor: haloColor,
188373
+ getWidth: haloWidth,
188374
+ widthUnits: "pixels",
188375
+ jointRounded: true,
188376
+ capRounded: true
188377
+ }));
188378
+ }
188379
+ if (sType === "line") {
188380
+ const { x1: x12, y1: y12, x2, y2 } = sel;
188381
+ selectionLayers.push(new PathLayer({
188382
+ ...base2,
188383
+ id: `annotation-select-${sUid}`,
188384
+ data: [{ path: [[x12, y12], [x2, y2]] }],
188385
+ getPath: (d) => d.path,
188386
+ getColor: haloColor,
188387
+ getWidth: haloWidth,
188388
+ widthUnits: "pixels",
188389
+ capRounded: true
188390
+ }));
188391
+ }
188392
+ if (sType === "ellipse") {
188393
+ const polygon2 = computeEllipsePolygon(sel.x1, sel.y1, sel.radiusX, sel.radiusY);
188394
+ selectionLayers.push(new PathLayer({
188395
+ ...base2,
188396
+ id: `annotation-select-${sUid}`,
188397
+ data: [{ path: [...polygon2, polygon2[0]] }],
188398
+ getPath: (d) => d.path,
188399
+ getColor: haloColor,
188400
+ getWidth: haloWidth,
188401
+ widthUnits: "pixels",
188402
+ jointRounded: true
188403
+ }));
188404
+ }
188405
+ if (sType === "polygon" && sel.points?.length >= 3) {
188406
+ selectionLayers.push(new PathLayer({
188407
+ ...base2,
188408
+ id: `annotation-select-${sUid}`,
188409
+ data: [{ path: [...sel.points, sel.points[0]] }],
188410
+ getPath: (d) => d.path,
188411
+ getColor: haloColor,
188412
+ getWidth: haloWidth,
188413
+ widthUnits: "pixels",
188414
+ jointRounded: true
188415
+ }));
188416
+ }
188417
+ if (sType === "polyline" && sel.points?.length >= 2) {
188418
+ selectionLayers.push(new PathLayer({
188419
+ ...base2,
188420
+ id: `annotation-select-${sUid}`,
188421
+ data: [{ path: sel.points }],
188422
+ getPath: (d) => d.path,
188423
+ getColor: haloColor,
188424
+ getWidth: haloWidth,
188425
+ widthUnits: "pixels",
188426
+ jointRounded: true,
188427
+ capRounded: true
188428
+ }));
188429
+ }
188430
+ }
188431
+ }
188432
+ const layers = [];
188433
+ const dotShapes = [];
188434
+ const clusterDotShapes = [];
188435
+ shapes.forEach((shape2, i2) => {
188436
+ const { uid: uid2, type: type2, strokeColor = [255, 255, 255], strokeWidth = 3, strokeDashArray } = shape2;
188437
+ const isSelected = uid2 === selectedShapeUid;
188438
+ let shapeOpacity = 1;
188439
+ let tipsOpacity = 1;
188440
+ let labelOpacity = 0;
188441
+ if (isSelected || !semanticZoom || authoredZoom === null) {
188442
+ tipsOpacity = 1;
188443
+ labelOpacity = 1;
188444
+ } else {
188445
+ const zoomRatio = 2 ** (zoom2 - authoredZoom);
188446
+ if (zoomRatio < RATIO_CLUSTER) {
188447
+ clusterDotShapes.push({ shape: shape2 });
188448
+ return;
188449
+ }
188450
+ if (zoomRatio < RATIO_DOT) {
188451
+ dotShapes.push({ shape: shape2, alpha: 200 });
188452
+ return;
188453
+ }
188454
+ if (zoomRatio < RATIO_GEO_FADE) {
188455
+ const t4 = (zoomRatio - RATIO_DOT) / (RATIO_GEO_FADE - RATIO_DOT);
188456
+ shapeOpacity = t4;
188457
+ dotShapes.push({ shape: shape2, alpha: Math.round((1 - t4) * 200) });
188458
+ }
188459
+ tipsOpacity = zoomRatio >= RATIO_TIPS ? shapeOpacity : Math.max(0, (zoomRatio - RATIO_GEO_FADE) / (RATIO_TIPS - RATIO_GEO_FADE) * shapeOpacity);
188460
+ if (zoomRatio >= RATIO_LABEL_FULL) {
188461
+ labelOpacity = shapeOpacity;
188462
+ } else if (zoomRatio >= RATIO_LABEL) {
188463
+ labelOpacity = (zoomRatio - RATIO_LABEL) / (RATIO_LABEL_FULL - RATIO_LABEL) * shapeOpacity;
188464
+ }
188465
+ }
188466
+ const arrowDataSize = Math.max(ARROW_SCREEN_PX, strokeWidth * 4) / 2 ** zoom2;
188467
+ const base2 = { coordinateSystem: COORDINATE_SYSTEM.CARTESIAN, opacity: shapeOpacity };
188468
+ const tipsBase = { coordinateSystem: COORDINATE_SYSTEM.CARTESIAN, opacity: tipsOpacity };
188469
+ const labelBase = { coordinateSystem: COORDINATE_SYSTEM.CARTESIAN, opacity: labelOpacity };
188470
+ const labelBgProps = shape2.labelBackground ? {
188471
+ background: true,
188472
+ getBackgroundColor: textBgColor(strokeColor),
188473
+ backgroundPadding: [3, 1, 3, 2]
188474
+ } : {};
188475
+ const labelProps = { ...labelBgProps };
188476
+ const dashArray = parseDashArray(strokeDashArray);
188477
+ const dashExt = [new PathStyleExtension({ dash: true })];
188478
+ const getDash = dashArray ?? [0, 0];
188479
+ if (type2 === "rectangle") {
188480
+ const { x: x2, y: y2, width: width2, height: height2, fillColor = strokeColor, fillOpacity = 0 } = shape2;
188481
+ const filled = fillOpacity > 0;
188482
+ const rectPolygon = [[x2, y2], [x2 + width2, y2], [x2 + width2, y2 + height2], [x2, y2 + height2]];
188483
+ layers.push(new PolygonLayer({
188484
+ ...base2,
188485
+ id: `annotation-rect-${uid2}-${i2}`,
188486
+ data: [{ polygon: rectPolygon }],
188487
+ getPolygon: (d) => d.polygon,
188488
+ filled,
188489
+ stroked: true,
188490
+ getFillColor: filled ? [...fillColor, Math.round(fillOpacity * 255)] : [0, 0, 0, 0],
188491
+ getLineColor: strokeColor,
188492
+ getLineWidth: strokeWidth,
188493
+ lineWidthUnits: "pixels",
188494
+ ...dashArray ? {
188495
+ extensions: [new PathStyleExtension({ dash: true })],
188496
+ getDashArray: dashArray
188497
+ } : {}
188498
+ }));
188499
+ if (shape2.text && labelOpacity > 0) {
188500
+ layers.push(new TextLayer({
188501
+ ...labelBase,
188502
+ ...labelProps,
188503
+ id: `annotation-rect-text-${uid2}-${i2}`,
188504
+ data: [{ position: [x2, y2], text: shape2.text }],
188505
+ getPosition: (d) => d.position,
188506
+ getText: (d) => d.text,
188507
+ getColor: strokeColor,
188508
+ getSize: LABEL_FONT_SIZE,
188509
+ sizeUnits: "pixels",
188510
+ fontFamily: LABEL_FONT_FAMILY,
188511
+ fontWeight: LABEL_FONT_WEIGHT,
188512
+ getPixelOffset: [3, -7],
188513
+ getTextAnchor: "start",
188514
+ getAlignmentBaseline: "bottom"
188515
+ }));
188516
+ }
188517
+ }
188518
+ if (type2 === "line") {
188519
+ const { x1: x12, y1: y12, x2, y2, markerStart, markerEnd } = shape2;
188520
+ const dx = x2 - x12;
188521
+ const dy = y2 - y12;
188522
+ const len2 = Math.sqrt(dx * dx + dy * dy);
188523
+ const ux = len2 > 0 ? dx / len2 : 0;
188524
+ const uy = len2 > 0 ? dy / len2 : 0;
188525
+ const srcX = markerStart === "Arrow" ? x12 + ux * arrowDataSize : x12;
188526
+ const srcY = markerStart === "Arrow" ? y12 + uy * arrowDataSize : y12;
188527
+ const tgtX = markerEnd === "Arrow" ? x2 - ux * arrowDataSize : x2;
188528
+ const tgtY = markerEnd === "Arrow" ? y2 - uy * arrowDataSize : y2;
188529
+ layers.push(new PathLayer({
188530
+ ...base2,
188531
+ id: `annotation-line-${uid2}-${i2}`,
188532
+ extensions: [new PathStyleExtension({ dash: true })],
188533
+ getDashArray: dashArray ?? [0, 0],
188534
+ data: [{ path: [[srcX, srcY], [tgtX, tgtY]] }],
188535
+ getPath: (d) => d.path,
188536
+ getColor: strokeColor,
188537
+ getWidth: strokeWidth,
188538
+ widthUnits: "pixels",
188539
+ capRounded: true
188540
+ }));
188541
+ if (markerEnd === "Arrow" && tipsOpacity > 0) {
188542
+ const head = computeArrowhead(x2, y2, x2 - x12, y2 - y12, arrowDataSize);
188543
+ if (head) {
188544
+ layers.push(new PolygonLayer({
188545
+ ...tipsBase,
188546
+ id: `annotation-end-arrow-${uid2}-${i2}`,
188547
+ data: [{ polygon: head }],
188548
+ getPolygon: (d) => d.polygon,
188549
+ filled: true,
188550
+ stroked: false,
188551
+ getFillColor: strokeColor
188552
+ }));
188553
+ }
188554
+ }
188555
+ if (markerStart === "Arrow" && tipsOpacity > 0) {
188556
+ const head = computeArrowhead(x12, y12, x12 - x2, y12 - y2, arrowDataSize);
188557
+ if (head) {
188558
+ layers.push(new PolygonLayer({
188559
+ ...tipsBase,
188560
+ id: `annotation-start-arrow-${uid2}-${i2}`,
188561
+ data: [{ polygon: head }],
188562
+ getPolygon: (d) => d.polygon,
188563
+ filled: true,
188564
+ stroked: false,
188565
+ getFillColor: strokeColor
188566
+ }));
188567
+ }
188568
+ }
188569
+ if (markerEnd === "Tick" && tipsOpacity > 0) {
188570
+ const tick2 = computeTickCap(x2, y2, x2 - x12, y2 - y12, arrowDataSize);
188571
+ if (tick2) {
188572
+ layers.push(new PathLayer({
188573
+ ...tipsBase,
188574
+ id: `annotation-end-tick-${uid2}-${i2}`,
188575
+ data: [{ path: tick2 }],
188576
+ getPath: (d) => d.path,
188577
+ getColor: strokeColor,
188578
+ getWidth: strokeWidth,
188579
+ widthUnits: "pixels",
188580
+ capRounded: true
188581
+ }));
188582
+ }
188583
+ }
188584
+ if (markerStart === "Tick" && tipsOpacity > 0) {
188585
+ const tick2 = computeTickCap(x12, y12, x12 - x2, y12 - y2, arrowDataSize);
188586
+ if (tick2) {
188587
+ layers.push(new PathLayer({
188588
+ ...tipsBase,
188589
+ id: `annotation-start-tick-${uid2}-${i2}`,
188590
+ data: [{ path: tick2 }],
188591
+ getPath: (d) => d.path,
188592
+ getColor: strokeColor,
188593
+ getWidth: strokeWidth,
188594
+ widthUnits: "pixels",
188595
+ capRounded: true
188596
+ }));
188597
+ }
188598
+ }
188599
+ if (shape2.text) {
188600
+ const pos = shape2.textPosition ?? "start";
188601
+ let textX;
188602
+ let textY;
188603
+ if (pos === "start") {
188604
+ textX = x12;
188605
+ textY = y12;
188606
+ } else if (pos === "end") {
188607
+ textX = x2;
188608
+ textY = y2;
188609
+ } else {
188610
+ textX = (x12 + x2) / 2;
188611
+ textY = (y12 + y2) / 2;
188612
+ }
188613
+ let pixelOffset = [0, 0];
188614
+ let textAnchor = "middle";
188615
+ let alignmentBaseline = "center";
188616
+ if (pos !== "middle") {
188617
+ const dxT = x2 - x12;
188618
+ const dyT = y2 - y12;
188619
+ const lenT = Math.sqrt(dxT * dxT + dyT * dyT);
188620
+ if (lenT > 0) {
188621
+ const uxT = dxT / lenT;
188622
+ const uyT = dyT / lenT;
188623
+ const sign2 = pos === "start" ? -1 : 1;
188624
+ const bufferPx = shape2.textBufferPx ?? 8;
188625
+ const offsetX = sign2 * uxT * bufferPx;
188626
+ const offsetY = sign2 * uyT * bufferPx;
188627
+ pixelOffset = [offsetX, offsetY];
188628
+ const absX = Math.abs(offsetX);
188629
+ const absY = Math.abs(offsetY);
188630
+ if (absX >= absY) {
188631
+ textAnchor = offsetX > 0 ? "start" : "end";
188632
+ }
188633
+ if (absY >= absX) {
188634
+ alignmentBaseline = offsetY > 0 ? "top" : "bottom";
188635
+ }
188636
+ }
188637
+ } else if (len2 > 0) {
188638
+ const belowPerp = pickBelowPerp(ux, uy);
188639
+ const bufferPx = shape2.textBufferPx ?? 14;
188640
+ pixelOffset = [-belowPerp[0] * bufferPx, -belowPerp[1] * bufferPx];
188641
+ textAnchor = "middle";
188642
+ alignmentBaseline = "center";
188643
+ }
188644
+ const middleAngle = pos === "middle" && len2 > 0 ? -readableAngle(Math.atan2(dy, dx) * 180 / Math.PI) : null;
188645
+ if (labelOpacity <= 0)
188646
+ return;
188647
+ layers.push(new TextLayer({
188648
+ ...labelBase,
188649
+ ...labelProps,
188650
+ id: `annotation-line-text-${uid2}-${i2}`,
188651
+ data: [{ position: [textX, textY], text: shape2.text }],
188652
+ getPosition: (d) => d.position,
188653
+ getText: (d) => d.text,
188654
+ getColor: strokeColor,
188655
+ getSize: LABEL_FONT_SIZE,
188656
+ sizeUnits: "pixels",
188657
+ fontFamily: LABEL_FONT_FAMILY,
188658
+ fontWeight: LABEL_FONT_WEIGHT,
188659
+ getPixelOffset: pixelOffset,
188660
+ getTextAnchor: textAnchor,
188661
+ getAlignmentBaseline: alignmentBaseline,
188662
+ ...middleAngle !== null && { getAngle: middleAngle }
188663
+ }));
188664
+ }
188665
+ }
188666
+ if (type2 === "ellipse") {
188667
+ const { x1: x12, y1: y12, radiusX, radiusY, fillColor = strokeColor, fillOpacity = 0 } = shape2;
188668
+ const polygon2 = computeEllipsePolygon(x12, y12, radiusX, radiusY);
188669
+ const filled = fillOpacity > 0;
188670
+ layers.push(new PolygonLayer({
188671
+ ...base2,
188672
+ id: `annotation-ellipse-${uid2}-${i2}`,
188673
+ data: [{ polygon: polygon2 }],
188674
+ getPolygon: (d) => d.polygon,
188675
+ filled,
188676
+ stroked: true,
188677
+ getFillColor: filled ? [...fillColor, Math.round(fillOpacity * 255)] : [0, 0, 0, 0],
188678
+ getLineColor: strokeColor,
188679
+ getLineWidth: strokeWidth,
188680
+ lineWidthUnits: "pixels",
188681
+ ...dashArray ? {
188682
+ extensions: [new PathStyleExtension({ dash: true })],
188683
+ getDashArray: dashArray
188684
+ } : {}
188685
+ }));
188686
+ if (shape2.text && labelOpacity > 0) {
188687
+ layers.push(new TextLayer({
188688
+ ...labelBase,
188689
+ ...labelProps,
188690
+ id: `annotation-ellipse-text-${uid2}-${i2}`,
188691
+ data: [{ position: [x12, y12 - radiusY], text: shape2.text }],
188692
+ getPosition: (d) => d.position,
188693
+ getText: (d) => d.text,
188694
+ getColor: strokeColor,
188695
+ getSize: LABEL_FONT_SIZE,
188696
+ sizeUnits: "pixels",
188697
+ fontFamily: LABEL_FONT_FAMILY,
188698
+ fontWeight: LABEL_FONT_WEIGHT,
188699
+ getPixelOffset: [0, -4],
188700
+ getTextAnchor: "middle",
188701
+ getAlignmentBaseline: "bottom"
188702
+ }));
188703
+ }
188704
+ }
188705
+ if (type2 === "polygon") {
188706
+ const { points: points2, fillColor = strokeColor, fillOpacity = 0 } = shape2;
188707
+ if (!points2 || points2.length < 3)
188708
+ return;
188709
+ const closedPath = [...points2, points2[0]];
188710
+ if (fillOpacity > 0) {
188711
+ layers.push(new PolygonLayer({
188712
+ ...base2,
188713
+ id: `annotation-polygon-fill-${uid2}-${i2}`,
188714
+ data: [{ polygon: points2 }],
188715
+ getPolygon: (d) => d.polygon,
188716
+ stroked: false,
188717
+ filled: true,
188718
+ getFillColor: [...fillColor, Math.round(fillOpacity * 255)]
188719
+ }));
188720
+ }
188721
+ layers.push(new PathLayer({
188722
+ ...base2,
188723
+ id: `annotation-polygon-${uid2}-${i2}`,
188724
+ extensions: dashExt,
188725
+ getDashArray: getDash,
188726
+ data: [{ path: closedPath }],
188727
+ getPath: (d) => d.path,
188728
+ getColor: strokeColor,
188729
+ getWidth: strokeWidth,
188730
+ widthUnits: "pixels",
188731
+ jointRounded: true
188732
+ }));
188733
+ if (shape2.text && labelOpacity > 0) {
188734
+ const minX = Math.min(...points2.map((p) => p[0]));
188735
+ const maxX = Math.max(...points2.map((p) => p[0]));
188736
+ const minY = Math.min(...points2.map((p) => p[1]));
188737
+ const topCenterX = (minX + maxX) / 2;
188738
+ layers.push(new TextLayer({
188739
+ ...labelBase,
188740
+ ...labelProps,
188741
+ id: `annotation-polygon-text-${uid2}-${i2}`,
188742
+ data: [{ position: [topCenterX, minY], text: shape2.text }],
188743
+ getPosition: (d) => d.position,
188744
+ getText: (d) => d.text,
188745
+ getColor: strokeColor,
188746
+ getSize: LABEL_FONT_SIZE,
188747
+ sizeUnits: "pixels",
188748
+ fontFamily: LABEL_FONT_FAMILY,
188749
+ fontWeight: LABEL_FONT_WEIGHT,
188750
+ getPixelOffset: [0, -4],
188751
+ getTextAnchor: "middle",
188752
+ getAlignmentBaseline: "bottom"
188753
+ }));
188754
+ }
188755
+ }
188756
+ if (type2 === "polyline") {
188757
+ const { points: points2, markerStart, markerEnd } = shape2;
188758
+ if (!points2 || points2.length < 2)
188759
+ return;
188760
+ let pathPoints = points2;
188761
+ if (markerStart === "Arrow") {
188762
+ const [x02, y02] = points2[0];
188763
+ const [x12, y12] = points2[1];
188764
+ const dx = x02 - x12;
188765
+ const dy = y02 - y12;
188766
+ const len2 = Math.sqrt(dx * dx + dy * dy);
188767
+ if (len2 > 0) {
188768
+ const ux = dx / len2;
188769
+ const uy = dy / len2;
188770
+ pathPoints = [[x02 - ux * arrowDataSize, y02 - uy * arrowDataSize], ...pathPoints.slice(1)];
188771
+ }
188772
+ }
188773
+ if (markerEnd === "Arrow") {
188774
+ const last = pathPoints[pathPoints.length - 1];
188775
+ const prev2 = pathPoints[pathPoints.length - 2];
188776
+ const dx = last[0] - prev2[0];
188777
+ const dy = last[1] - prev2[1];
188778
+ const len2 = Math.sqrt(dx * dx + dy * dy);
188779
+ if (len2 > 0) {
188780
+ const ux = dx / len2;
188781
+ const uy = dy / len2;
188782
+ const shortened = [last[0] - ux * arrowDataSize, last[1] - uy * arrowDataSize];
188783
+ pathPoints = [...pathPoints.slice(0, -1), shortened];
188784
+ }
188785
+ }
188786
+ layers.push(new PathLayer({
188787
+ ...base2,
188788
+ id: `annotation-polyline-${uid2}-${i2}`,
188789
+ extensions: dashExt,
188790
+ getDashArray: getDash,
188791
+ data: [{ path: pathPoints }],
188792
+ getPath: (d) => d.path,
188793
+ getColor: strokeColor,
188794
+ getWidth: strokeWidth,
188795
+ widthUnits: "pixels",
188796
+ jointRounded: true,
188797
+ capRounded: true
188798
+ }));
188799
+ if (markerStart === "Arrow" && tipsOpacity > 0) {
188800
+ const [x02, y02] = points2[0];
188801
+ const [x12, y12] = points2[1];
188802
+ const head = computeArrowhead(x02, y02, x02 - x12, y02 - y12, arrowDataSize);
188803
+ if (head) {
188804
+ layers.push(new PolygonLayer({
188805
+ ...tipsBase,
188806
+ id: `annotation-polyline-start-arrow-${uid2}-${i2}`,
188807
+ data: [{ polygon: head }],
188808
+ getPolygon: (d) => d.polygon,
188809
+ filled: true,
188810
+ stroked: false,
188811
+ getFillColor: strokeColor
188812
+ }));
188813
+ }
188814
+ }
188815
+ if (markerEnd === "Arrow" && tipsOpacity > 0) {
188816
+ const last = points2[points2.length - 1];
188817
+ const prev2 = points2[points2.length - 2];
188818
+ const dxArrow = last[0] - prev2[0];
188819
+ const dyArrow = last[1] - prev2[1];
188820
+ const head = computeArrowhead(last[0], last[1], dxArrow, dyArrow, arrowDataSize);
188821
+ if (head) {
188822
+ layers.push(new PolygonLayer({
188823
+ ...tipsBase,
188824
+ id: `annotation-polyline-end-arrow-${uid2}-${i2}`,
188825
+ data: [{ polygon: head }],
188826
+ getPolygon: (d) => d.polygon,
188827
+ filled: true,
188828
+ stroked: false,
188829
+ getFillColor: strokeColor
188830
+ }));
188831
+ }
188832
+ }
188833
+ if (markerStart === "Tick" && tipsOpacity > 0) {
188834
+ const [x02, y02] = points2[0];
188835
+ const [x12, y12] = points2[1];
188836
+ const tick2 = computeTickCap(x02, y02, x02 - x12, y02 - y12, arrowDataSize);
188837
+ if (tick2) {
188838
+ layers.push(new PathLayer({
188839
+ ...tipsBase,
188840
+ id: `annotation-polyline-start-tick-${uid2}-${i2}`,
188841
+ data: [{ path: tick2 }],
188842
+ getPath: (d) => d.path,
188843
+ getColor: strokeColor,
188844
+ getWidth: strokeWidth,
188845
+ widthUnits: "pixels",
188846
+ capRounded: true
188847
+ }));
188848
+ }
188849
+ }
188850
+ if (markerEnd === "Tick" && tipsOpacity > 0) {
188851
+ const last = points2[points2.length - 1];
188852
+ const prev2 = points2[points2.length - 2];
188853
+ const dxTick = last[0] - prev2[0];
188854
+ const dyTick = last[1] - prev2[1];
188855
+ const tick2 = computeTickCap(last[0], last[1], dxTick, dyTick, arrowDataSize);
188856
+ if (tick2) {
188857
+ layers.push(new PathLayer({
188858
+ ...tipsBase,
188859
+ id: `annotation-polyline-end-tick-${uid2}-${i2}`,
188860
+ data: [{ path: tick2 }],
188861
+ getPath: (d) => d.path,
188862
+ getColor: strokeColor,
188863
+ getWidth: strokeWidth,
188864
+ widthUnits: "pixels",
188865
+ capRounded: true
188866
+ }));
188867
+ }
188868
+ }
188869
+ if (shape2.text) {
188870
+ const pos = shape2.textPosition ?? "start";
188871
+ const bufferPx = shape2.textBufferPx ?? 8;
188872
+ let textPt;
188873
+ let dirX;
188874
+ let dirY;
188875
+ if (pos === "end") {
188876
+ const last = points2[points2.length - 1];
188877
+ const prev2 = points2[points2.length - 2];
188878
+ textPt = last;
188879
+ dirX = last[0] - prev2[0];
188880
+ dirY = last[1] - prev2[1];
188881
+ } else if (pos === "middle") {
188882
+ const mx = points2.reduce((s2, p) => s2 + p[0], 0) / points2.length;
188883
+ const my = points2.reduce((s2, p) => s2 + p[1], 0) / points2.length;
188884
+ textPt = [mx, my];
188885
+ dirX = 0;
188886
+ dirY = 0;
188887
+ } else {
188888
+ [textPt] = points2;
188889
+ dirX = points2[0][0] - points2[1][0];
188890
+ dirY = points2[0][1] - points2[1][1];
188891
+ }
188892
+ const [textX, textY] = textPt;
188893
+ const lenT = Math.sqrt(dirX * dirX + dirY * dirY);
188894
+ let polylinePixelOffset = [0, 0];
188895
+ let polylineTextAnchor = "middle";
188896
+ let polylineAlignmentBaseline = pos === "middle" ? "center" : "bottom";
188897
+ if (lenT > 0) {
188898
+ const uxT = dirX / lenT;
188899
+ const uyT = dirY / lenT;
188900
+ const offX = uxT * bufferPx;
188901
+ const offY = uyT * bufferPx;
188902
+ polylinePixelOffset = [offX, offY];
188903
+ if (Math.abs(offX) >= Math.abs(offY)) {
188904
+ polylineTextAnchor = offX > 0 ? "start" : "end";
188905
+ }
188906
+ if (Math.abs(offY) >= Math.abs(offX)) {
188907
+ polylineAlignmentBaseline = offY > 0 ? "top" : "bottom";
188908
+ }
188909
+ }
188910
+ if (labelOpacity <= 0)
188911
+ return;
188912
+ layers.push(new TextLayer({
188913
+ ...labelBase,
188914
+ ...labelProps,
188915
+ id: `annotation-polyline-text-${uid2}-${i2}`,
188916
+ data: [{ position: [textX, textY], text: shape2.text }],
188917
+ getPosition: (d) => d.position,
188918
+ getText: (d) => d.text,
188919
+ getColor: strokeColor,
188920
+ getSize: LABEL_FONT_SIZE,
188921
+ sizeUnits: "pixels",
188922
+ fontFamily: LABEL_FONT_FAMILY,
188923
+ fontWeight: LABEL_FONT_WEIGHT,
188924
+ getPixelOffset: polylinePixelOffset,
188925
+ getTextAnchor: polylineTextAnchor,
188926
+ getAlignmentBaseline: polylineAlignmentBaseline
188927
+ }));
188928
+ }
188929
+ }
188930
+ });
188931
+ if (dotShapes.length > 0) {
188932
+ layers.push(new ScatterplotLayer({
188933
+ coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
188934
+ id: "annotation-dots",
188935
+ data: dotShapes.map(({ shape: s2, alpha: alpha2 }) => ({
188936
+ position: getShapeCentroid(s2),
188937
+ color: [...s2.strokeColor ?? [255, 255, 255], alpha2]
188938
+ })),
188939
+ getPosition: (d) => d.position,
188940
+ getFillColor: (d) => d.color,
188941
+ getRadius: 4,
188942
+ radiusUnits: "pixels",
188943
+ filled: true,
188944
+ stroked: false
188945
+ }));
188946
+ }
188947
+ if (clusterDotShapes.length > 0) {
188948
+ const clusters = computeClusters(clusterDotShapes, zoom2);
188949
+ layers.push(new ScatterplotLayer({
188950
+ coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
188951
+ id: "annotation-clusters",
188952
+ data: clusters,
188953
+ getPosition: (d) => d.position,
188954
+ getFillColor: (d) => [...d.color, 180],
188955
+ getRadius: (d) => Math.max(6, 4 + Math.sqrt(d.count) * 2),
188956
+ radiusUnits: "pixels",
188957
+ filled: true,
188958
+ stroked: true,
188959
+ getLineColor: (d) => [...d.color, 220],
188960
+ lineWidthMinPixels: 1.5
188961
+ }));
188962
+ const multiClusters = clusters.filter((c2) => c2.count > 1);
188963
+ if (multiClusters.length > 0) {
188964
+ layers.push(new TextLayer({
188965
+ coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
188966
+ id: "annotation-cluster-counts",
188967
+ data: multiClusters,
188968
+ getPosition: (d) => d.position,
188969
+ getText: (d) => String(d.count),
188970
+ getColor: [255, 255, 255],
188971
+ getSize: 10,
188972
+ sizeUnits: "pixels",
188973
+ fontWeight: "bold",
188974
+ getTextAnchor: "middle",
188975
+ getAlignmentBaseline: "center"
188976
+ }));
188977
+ }
188978
+ }
188979
+ const measureLayers = [];
188980
+ shapes.forEach((shape2) => {
188981
+ if (!shape2.showMeasure)
188982
+ return;
188983
+ const mIsSelected = shape2.uid === selectedShapeUid;
188984
+ const label2 = getMeasurementLabel(shape2, physicalPixelSize, viewType);
188985
+ if (!label2)
188986
+ return;
188987
+ const color2 = shape2.strokeColor ?? [255, 255, 255];
188988
+ let mShapeOpacity = 1;
188989
+ let mLabelFactor = 0;
188990
+ if (mIsSelected || !semanticZoom || authoredZoom === null) {
188991
+ mLabelFactor = 1;
188992
+ } else {
188993
+ const mZoomRatio = 2 ** (zoom2 - authoredZoom);
188994
+ if (mZoomRatio < RATIO_DOT)
188995
+ return;
188996
+ if (mZoomRatio < RATIO_GEO_FADE) {
188997
+ mShapeOpacity = (mZoomRatio - RATIO_DOT) / (RATIO_GEO_FADE - RATIO_DOT);
188998
+ }
188999
+ if (mZoomRatio >= RATIO_LABEL_FULL) {
189000
+ mLabelFactor = 1;
189001
+ } else if (mZoomRatio >= RATIO_LABEL) {
189002
+ mLabelFactor = (mZoomRatio - RATIO_LABEL) / (RATIO_LABEL_FULL - RATIO_LABEL);
189003
+ }
189004
+ }
189005
+ const measureOpacity = mShapeOpacity * mLabelFactor;
189006
+ if (measureOpacity <= 0)
189007
+ return;
189008
+ const base2 = {
189009
+ coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
189010
+ opacity: measureOpacity,
189011
+ getText: (d) => d.text,
189012
+ getPosition: (d) => d.position,
189013
+ getColor: color2,
189014
+ getSize: 13,
189015
+ sizeUnits: "pixels",
189016
+ fontFamily: LABEL_FONT_FAMILY,
189017
+ fontWeight: "bold",
189018
+ // 'auto' builds the font atlas from the actual rendered characters (e.g. µ, ²)
189019
+ // instead of deck.gl's default ASCII-only atlas.
189020
+ characterSet: "auto",
189021
+ ...shape2.measureBackground ? {
189022
+ background: true,
189023
+ getBackgroundColor: textBgColor(color2),
189024
+ backgroundPadding: [3, 1, 3, 2]
189025
+ } : {}
189026
+ };
189027
+ if (shape2.type === "line") {
189028
+ const mx = ((shape2.x1 ?? 0) + (shape2.x2 ?? 0)) / 2;
189029
+ const my = ((shape2.y1 ?? 0) + (shape2.y2 ?? 0)) / 2;
189030
+ const dx = (shape2.x2 ?? 0) - (shape2.x1 ?? 0);
189031
+ const dy = (shape2.y2 ?? 0) - (shape2.y1 ?? 0);
189032
+ const len2 = Math.sqrt(dx * dx + dy * dy);
189033
+ if (len2 === 0)
189034
+ return;
189035
+ const perp = pickBelowPerp(dx / len2, dy / len2);
189036
+ const angle2 = readableAngle(Math.atan2(dy, dx) * 180 / Math.PI);
189037
+ measureLayers.push(new TextLayer({
189038
+ ...base2,
189039
+ id: `annotation-measure-${shape2.uid}`,
189040
+ data: [{ position: [mx, my], text: label2 }],
189041
+ getAngle: -angle2,
189042
+ getTextAnchor: "middle",
189043
+ getAlignmentBaseline: "center",
189044
+ getPixelOffset: [perp[0] * 16, perp[1] * 16]
189045
+ }));
189046
+ } else if (shape2.type === "polyline" && shape2.points?.length >= 2) {
189047
+ const pts = shape2.points;
189048
+ const mi = Math.floor((pts.length - 1) / 2);
189049
+ const [ax, ay] = pts[mi];
189050
+ const [bx, by] = pts[mi + 1];
189051
+ const dx = bx - ax;
189052
+ const dy = by - ay;
189053
+ const len2 = Math.sqrt(dx * dx + dy * dy);
189054
+ if (len2 === 0)
189055
+ return;
189056
+ const perp = pickBelowPerp(dx / len2, dy / len2);
189057
+ const angle2 = readableAngle(Math.atan2(dy, dx) * 180 / Math.PI);
189058
+ measureLayers.push(new TextLayer({
189059
+ ...base2,
189060
+ id: `annotation-measure-${shape2.uid}`,
189061
+ data: [{ position: [(ax + bx) / 2, (ay + by) / 2], text: label2 }],
189062
+ getAngle: -angle2,
189063
+ getTextAnchor: "middle",
189064
+ getAlignmentBaseline: "center",
189065
+ getPixelOffset: [perp[0] * 16, perp[1] * 16]
189066
+ }));
189067
+ } else if (shape2.type === "rectangle") {
189068
+ const x02 = shape2.x ?? 0;
189069
+ const y02 = shape2.y ?? 0;
189070
+ const w2 = shape2.width ?? 0;
189071
+ const h2 = shape2.height ?? 0;
189072
+ const rx = x02 + (w2 >= 0 ? w2 : 0);
189073
+ const ry = y02 + (h2 >= 0 ? h2 : 0);
189074
+ measureLayers.push(new TextLayer({
189075
+ ...base2,
189076
+ id: `annotation-measure-${shape2.uid}`,
189077
+ data: [{ position: [rx, ry], text: label2 }],
189078
+ getTextAnchor: "end",
189079
+ getAlignmentBaseline: "bottom",
189080
+ getPixelOffset: [-6, -6]
189081
+ }));
189082
+ } else if (shape2.type === "ellipse") {
189083
+ const cx = shape2.x1 ?? 0;
189084
+ const cy = shape2.y1 ?? 0;
189085
+ const ry = Math.abs(shape2.radiusY ?? 0);
189086
+ measureLayers.push(new TextLayer({
189087
+ ...base2,
189088
+ id: `annotation-measure-${shape2.uid}`,
189089
+ data: [{ position: [cx, cy + ry], text: label2 }],
189090
+ getTextAnchor: "middle",
189091
+ getAlignmentBaseline: "top",
189092
+ getPixelOffset: [0, 8]
189093
+ }));
189094
+ } else if (shape2.type === "polygon" && shape2.points?.length >= 3) {
189095
+ const xs = shape2.points.map((p) => p[0]);
189096
+ const ys = shape2.points.map((p) => p[1]);
189097
+ const cx = (Math.min(...xs) + Math.max(...xs)) / 2;
189098
+ const maxY2 = Math.max(...ys);
189099
+ measureLayers.push(new TextLayer({
189100
+ ...base2,
189101
+ id: `annotation-measure-${shape2.uid}`,
189102
+ data: [{ position: [cx, maxY2], text: label2 }],
189103
+ getTextAnchor: "middle",
189104
+ getAlignmentBaseline: "top",
189105
+ getPixelOffset: [0, 8]
189106
+ }));
189107
+ }
189108
+ });
189109
+ return [...selectionLayers, ...layers, ...measureLayers];
189110
+ }
189111
+ const defaultProps = {
189112
+ // Array of annotation shape objects (see annotationShapeObj in @vitessce/schemas).
189113
+ data: { type: "object", value: [], compare: true },
189114
+ // UID of the shape to highlight with a selection halo.
189115
+ selectedShapeUid: { type: "string", value: null },
189116
+ // { x, y, unit } used to format measurement labels in physical units.
189117
+ physicalPixelSize: { type: "object", value: null, compare: true },
189118
+ // The zoom level at which the shapes were authored (reference for semantic zoom).
189119
+ authoredZoom: { type: "number", value: null },
189120
+ // Whether to apply semantic zoom (level-of-detail) based on authoredZoom.
189121
+ semanticZoom: { type: "boolean", value: true },
189122
+ // The view type in which the layer is rendered (affects measurement label formatting).
189123
+ viewType: { type: "string", value: ViewType$1.SPATIAL },
189124
+ // In-progress shape being drawn, as { type, vertices }.
189125
+ inProgressShape: { type: "object", value: null, compare: true },
189126
+ // Current mouse position (in data coordinates) while drawing.
189127
+ hoverCoord: { type: "array", value: null, compare: true },
189128
+ // Stroke color [r, g, b] of the in-progress shape preview.
189129
+ previewStrokeColor: { type: "color", value: [255, 255, 0] }
189130
+ };
189131
+ class AnnotationLayer extends CompositeLayer {
189132
+ // The semantic zoom and arrowhead sizes depend on the current zoom level,
189133
+ // so the sublayers must also be re-rendered when the viewport changes.
189134
+ // eslint-disable-next-line class-methods-use-this
189135
+ shouldUpdateState({ changeFlags }) {
189136
+ return changeFlags.propsOrDataChanged || changeFlags.viewportChanged;
189137
+ }
189138
+ /**
189139
+ * Prefix the sublayer ID with this layer's ID (so that multiple
189140
+ * AnnotationLayers can co-exist), and combine this layer's
189141
+ * opacity with the per-shape opacity of the sublayer.
189142
+ * Note: we avoid getSubLayerProps here, since it would
189143
+ * overwrite the sublayer extensions (e.g., PathStyleExtension)
189144
+ * and opacity values.
189145
+ * @param {object} layer A DeckGL layer instance.
189146
+ * @returns {object} The cloned layer instance.
189147
+ */
189148
+ wrapSubLayer(layer) {
189149
+ const { id: id2, opacity: opacity2, pickable, visible } = this.props;
189150
+ return layer.clone({
189151
+ id: `${id2}-${layer.id}`,
189152
+ opacity: layer.props.opacity * opacity2,
189153
+ pickable,
189154
+ visible
189155
+ });
189156
+ }
189157
+ renderLayers() {
189158
+ const { data: data2, selectedShapeUid, physicalPixelSize, authoredZoom, semanticZoom, viewType, inProgressShape, hoverCoord, previewStrokeColor } = this.props;
189159
+ const zoom2 = this.context.viewport?.zoom ?? 0;
189160
+ const visibleShapes = (data2 ?? []).filter((shape2) => shape2.visible !== false);
189161
+ const annotationLayers = createAnnotationLayers(visibleShapes, zoom2, selectedShapeUid, physicalPixelSize, authoredZoom, semanticZoom, viewType);
189162
+ const previewLayer = createPreviewLayer(inProgressShape, hoverCoord, previewStrokeColor.slice(0, 3));
189163
+ return [
189164
+ ...annotationLayers,
189165
+ ...previewLayer ? [previewLayer] : []
189166
+ ].map((layer) => this.wrapSubLayer(layer));
189167
+ }
189168
+ }
189169
+ AnnotationLayer.layerName = "AnnotationLayer";
189170
+ AnnotationLayer.defaultProps = defaultProps;
187808
189171
  const FEATURE_AGGREGATION_STRATEGIES = ["first", "last", "sum", "mean"];
187809
189172
  function CellSetExpressionPlotOptions(props) {
187810
189173
  const {