@tldraw/commenting 0.0.0-bootstrap
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/commenting.css +832 -0
- package/dist-cjs/canvas/cluster-input.js +40 -0
- package/dist-cjs/canvas/cluster-input.js.map +7 -0
- package/dist-cjs/canvas/comment-body.js +31 -0
- package/dist-cjs/canvas/comment-body.js.map +7 -0
- package/dist-cjs/canvas/comment-render.js +72 -0
- package/dist-cjs/canvas/comment-render.js.map +7 -0
- package/dist-cjs/canvas/comment-store.js +48 -0
- package/dist-cjs/canvas/comment-store.js.map +7 -0
- package/dist-cjs/canvas/comment-tool.js +160 -0
- package/dist-cjs/canvas/comment-tool.js.map +7 -0
- package/dist-cjs/canvas/comments-filter-menu.js +109 -0
- package/dist-cjs/canvas/comments-filter-menu.js.map +7 -0
- package/dist-cjs/canvas/comments-menu-item.js +42 -0
- package/dist-cjs/canvas/comments-menu-item.js.map +7 -0
- package/dist-cjs/canvas/comments-overflow-menu.js +64 -0
- package/dist-cjs/canvas/comments-overflow-menu.js.map +7 -0
- package/dist-cjs/canvas/comments-overlay.js +1081 -0
- package/dist-cjs/canvas/comments-overlay.js.map +7 -0
- package/dist-cjs/canvas/comments-sidebar.js +134 -0
- package/dist-cjs/canvas/comments-sidebar.js.map +7 -0
- package/dist-cjs/canvas/hooks.js +45 -0
- package/dist-cjs/canvas/hooks.js.map +7 -0
- package/dist-cjs/canvas/license.js +28 -0
- package/dist-cjs/canvas/license.js.map +7 -0
- package/dist-cjs/canvas/options.js +45 -0
- package/dist-cjs/canvas/options.js.map +7 -0
- package/dist-cjs/canvas/region-options.js +49 -0
- package/dist-cjs/canvas/region-options.js.map +7 -0
- package/dist-cjs/canvas/rich-text.js +28 -0
- package/dist-cjs/canvas/rich-text.js.map +7 -0
- package/dist-cjs/canvas/sidebar-filters.js +30 -0
- package/dist-cjs/canvas/sidebar-filters.js.map +7 -0
- package/dist-cjs/canvas/state.js +76 -0
- package/dist-cjs/canvas/state.js.map +7 -0
- package/dist-cjs/canvas/thread-state.js +81 -0
- package/dist-cjs/canvas/thread-state.js.map +7 -0
- package/dist-cjs/clustering/computeClusterTable.js +83 -0
- package/dist-cjs/clustering/computeClusterTable.js.map +7 -0
- package/dist-cjs/clustering/mst.js +117 -0
- package/dist-cjs/clustering/mst.js.map +7 -0
- package/dist-cjs/clustering/replay.js +275 -0
- package/dist-cjs/clustering/replay.js.map +7 -0
- package/dist-cjs/clustering/runtime.js +134 -0
- package/dist-cjs/clustering/runtime.js.map +7 -0
- package/dist-cjs/clustering/schedule.js +170 -0
- package/dist-cjs/clustering/schedule.js.map +7 -0
- package/dist-cjs/clustering/types.js +17 -0
- package/dist-cjs/clustering/types.js.map +7 -0
- package/dist-cjs/index.d.ts +868 -0
- package/dist-cjs/index.js +126 -0
- package/dist-cjs/index.js.map +7 -0
- package/dist-cjs/ui/avatar.js +43 -0
- package/dist-cjs/ui/avatar.js.map +7 -0
- package/dist-cjs/ui/byline.js +35 -0
- package/dist-cjs/ui/byline.js.map +7 -0
- package/dist-cjs/ui/comment-card.js +37 -0
- package/dist-cjs/ui/comment-card.js.map +7 -0
- package/dist-cjs/ui/comment-composer.js +136 -0
- package/dist-cjs/ui/comment-composer.js.map +7 -0
- package/dist-cjs/ui/comment-extensions.js +37 -0
- package/dist-cjs/ui/comment-extensions.js.map +7 -0
- package/dist-cjs/ui/comment-mention.js +42 -0
- package/dist-cjs/ui/comment-mention.js.map +7 -0
- package/dist-cjs/ui/comment-pin.js +29 -0
- package/dist-cjs/ui/comment-pin.js.map +7 -0
- package/dist-cjs/ui/comment-text.js +29 -0
- package/dist-cjs/ui/comment-text.js.map +7 -0
- package/dist-cjs/ui/comment-thread.js +45 -0
- package/dist-cjs/ui/comment-thread.js.map +7 -0
- package/dist-cjs/ui/comments-list.js +106 -0
- package/dist-cjs/ui/comments-list.js.map +7 -0
- package/dist-cjs/ui/count-badge.js +28 -0
- package/dist-cjs/ui/count-badge.js.map +7 -0
- package/dist-cjs/ui/empty-state.js +31 -0
- package/dist-cjs/ui/empty-state.js.map +7 -0
- package/dist-cjs/ui/format-time.js +46 -0
- package/dist-cjs/ui/format-time.js.map +7 -0
- package/dist-cjs/ui/mention-list.js +70 -0
- package/dist-cjs/ui/mention-list.js.map +7 -0
- package/dist-cjs/ui/mention-suggestion.js +206 -0
- package/dist-cjs/ui/mention-suggestion.js.map +7 -0
- package/dist-cjs/ui/mention.js +31 -0
- package/dist-cjs/ui/mention.js.map +7 -0
- package/dist-cjs/ui/reaction.js +31 -0
- package/dist-cjs/ui/reaction.js.map +7 -0
- package/dist-cjs/ui/reactions.js +34 -0
- package/dist-cjs/ui/reactions.js.map +7 -0
- package/dist-cjs/ui/render-markdown.js +63 -0
- package/dist-cjs/ui/render-markdown.js.map +7 -0
- package/dist-cjs/ui/send-button.js +28 -0
- package/dist-cjs/ui/send-button.js.map +7 -0
- package/dist-esm/canvas/cluster-input.mjs +20 -0
- package/dist-esm/canvas/cluster-input.mjs.map +7 -0
- package/dist-esm/canvas/comment-body.mjs +11 -0
- package/dist-esm/canvas/comment-body.mjs.map +7 -0
- package/dist-esm/canvas/comment-render.mjs +52 -0
- package/dist-esm/canvas/comment-render.mjs.map +7 -0
- package/dist-esm/canvas/comment-store.mjs +28 -0
- package/dist-esm/canvas/comment-store.mjs.map +7 -0
- package/dist-esm/canvas/comment-tool.mjs +142 -0
- package/dist-esm/canvas/comment-tool.mjs.map +7 -0
- package/dist-esm/canvas/comments-filter-menu.mjs +99 -0
- package/dist-esm/canvas/comments-filter-menu.mjs.map +7 -0
- package/dist-esm/canvas/comments-menu-item.mjs +22 -0
- package/dist-esm/canvas/comments-menu-item.mjs.map +7 -0
- package/dist-esm/canvas/comments-overflow-menu.mjs +53 -0
- package/dist-esm/canvas/comments-overflow-menu.mjs.map +7 -0
- package/dist-esm/canvas/comments-overlay.mjs +1094 -0
- package/dist-esm/canvas/comments-overlay.mjs.map +7 -0
- package/dist-esm/canvas/comments-sidebar.mjs +120 -0
- package/dist-esm/canvas/comments-sidebar.mjs.map +7 -0
- package/dist-esm/canvas/hooks.mjs +25 -0
- package/dist-esm/canvas/hooks.mjs.map +7 -0
- package/dist-esm/canvas/license.mjs +8 -0
- package/dist-esm/canvas/license.mjs.map +7 -0
- package/dist-esm/canvas/options.mjs +27 -0
- package/dist-esm/canvas/options.mjs.map +7 -0
- package/dist-esm/canvas/region-options.mjs +29 -0
- package/dist-esm/canvas/region-options.mjs.map +7 -0
- package/dist-esm/canvas/rich-text.mjs +8 -0
- package/dist-esm/canvas/rich-text.mjs.map +7 -0
- package/dist-esm/canvas/sidebar-filters.mjs +10 -0
- package/dist-esm/canvas/sidebar-filters.mjs.map +7 -0
- package/dist-esm/canvas/state.mjs +60 -0
- package/dist-esm/canvas/state.mjs.map +7 -0
- package/dist-esm/canvas/thread-state.mjs +61 -0
- package/dist-esm/canvas/thread-state.mjs.map +7 -0
- package/dist-esm/clustering/computeClusterTable.mjs +63 -0
- package/dist-esm/clustering/computeClusterTable.mjs.map +7 -0
- package/dist-esm/clustering/mst.mjs +97 -0
- package/dist-esm/clustering/mst.mjs.map +7 -0
- package/dist-esm/clustering/replay.mjs +255 -0
- package/dist-esm/clustering/replay.mjs.map +7 -0
- package/dist-esm/clustering/runtime.mjs +114 -0
- package/dist-esm/clustering/runtime.mjs.map +7 -0
- package/dist-esm/clustering/schedule.mjs +150 -0
- package/dist-esm/clustering/schedule.mjs.map +7 -0
- package/dist-esm/clustering/types.mjs +1 -0
- package/dist-esm/clustering/types.mjs.map +7 -0
- package/dist-esm/index.d.mts +868 -0
- package/dist-esm/index.mjs +139 -0
- package/dist-esm/index.mjs.map +7 -0
- package/dist-esm/ui/avatar.mjs +23 -0
- package/dist-esm/ui/avatar.mjs.map +7 -0
- package/dist-esm/ui/byline.mjs +15 -0
- package/dist-esm/ui/byline.mjs.map +7 -0
- package/dist-esm/ui/comment-card.mjs +17 -0
- package/dist-esm/ui/comment-card.mjs.map +7 -0
- package/dist-esm/ui/comment-composer.mjs +116 -0
- package/dist-esm/ui/comment-composer.mjs.map +7 -0
- package/dist-esm/ui/comment-extensions.mjs +17 -0
- package/dist-esm/ui/comment-extensions.mjs.map +7 -0
- package/dist-esm/ui/comment-mention.mjs +22 -0
- package/dist-esm/ui/comment-mention.mjs.map +7 -0
- package/dist-esm/ui/comment-pin.mjs +9 -0
- package/dist-esm/ui/comment-pin.mjs.map +7 -0
- package/dist-esm/ui/comment-text.mjs +9 -0
- package/dist-esm/ui/comment-text.mjs.map +7 -0
- package/dist-esm/ui/comment-thread.mjs +25 -0
- package/dist-esm/ui/comment-thread.mjs.map +7 -0
- package/dist-esm/ui/comments-list.mjs +86 -0
- package/dist-esm/ui/comments-list.mjs.map +7 -0
- package/dist-esm/ui/count-badge.mjs +8 -0
- package/dist-esm/ui/count-badge.mjs.map +7 -0
- package/dist-esm/ui/empty-state.mjs +11 -0
- package/dist-esm/ui/empty-state.mjs.map +7 -0
- package/dist-esm/ui/format-time.mjs +26 -0
- package/dist-esm/ui/format-time.mjs.map +7 -0
- package/dist-esm/ui/mention-list.mjs +50 -0
- package/dist-esm/ui/mention-list.mjs.map +7 -0
- package/dist-esm/ui/mention-suggestion.mjs +186 -0
- package/dist-esm/ui/mention-suggestion.mjs.map +7 -0
- package/dist-esm/ui/mention.mjs +11 -0
- package/dist-esm/ui/mention.mjs.map +7 -0
- package/dist-esm/ui/reaction.mjs +11 -0
- package/dist-esm/ui/reaction.mjs.map +7 -0
- package/dist-esm/ui/reactions.mjs +14 -0
- package/dist-esm/ui/reactions.mjs.map +7 -0
- package/dist-esm/ui/render-markdown.mjs +45 -0
- package/dist-esm/ui/render-markdown.mjs.map +7 -0
- package/dist-esm/ui/send-button.mjs +8 -0
- package/dist-esm/ui/send-button.mjs.map +7 -0
- package/package.json +71 -0
- package/src/canvas/canvas.css +148 -0
- package/src/canvas/cluster-input.test.ts +223 -0
- package/src/canvas/cluster-input.ts +29 -0
- package/src/canvas/comment-body.tsx +21 -0
- package/src/canvas/comment-render.test.ts +90 -0
- package/src/canvas/comment-render.ts +92 -0
- package/src/canvas/comment-store.ts +55 -0
- package/src/canvas/comment-tool.tsx +200 -0
- package/src/canvas/comments-filter-menu.tsx +104 -0
- package/src/canvas/comments-menu-item.tsx +24 -0
- package/src/canvas/comments-overflow-menu.tsx +62 -0
- package/src/canvas/comments-overlay.tsx +1460 -0
- package/src/canvas/comments-sidebar.tsx +184 -0
- package/src/canvas/hooks.ts +28 -0
- package/src/canvas/license.ts +15 -0
- package/src/canvas/options.test.ts +127 -0
- package/src/canvas/options.ts +111 -0
- package/src/canvas/region-options.ts +57 -0
- package/src/canvas/rich-text.ts +15 -0
- package/src/canvas/sidebar-filters.ts +25 -0
- package/src/canvas/state.ts +102 -0
- package/src/canvas/thread-state.ts +91 -0
- package/src/clustering/computeClusterTable.test.ts +299 -0
- package/src/clustering/computeClusterTable.ts +82 -0
- package/src/clustering/locality.test.ts +189 -0
- package/src/clustering/mst.test.ts +383 -0
- package/src/clustering/mst.ts +134 -0
- package/src/clustering/replay.test.ts +547 -0
- package/src/clustering/replay.ts +326 -0
- package/src/clustering/runtime.test.ts +596 -0
- package/src/clustering/runtime.ts +153 -0
- package/src/clustering/schedule.test.ts +514 -0
- package/src/clustering/schedule.ts +184 -0
- package/src/clustering/types.ts +102 -0
- package/src/index.ts +96 -0
- package/src/ui/avatar.tsx +31 -0
- package/src/ui/byline.tsx +23 -0
- package/src/ui/comment-card.tsx +32 -0
- package/src/ui/comment-composer.tsx +179 -0
- package/src/ui/comment-extensions.ts +25 -0
- package/src/ui/comment-mention.ts +47 -0
- package/src/ui/comment-pin.tsx +21 -0
- package/src/ui/comment-text.tsx +12 -0
- package/src/ui/comment-thread.tsx +56 -0
- package/src/ui/comments-list.tsx +139 -0
- package/src/ui/comments.css +678 -0
- package/src/ui/count-badge.tsx +9 -0
- package/src/ui/empty-state.tsx +16 -0
- package/src/ui/format-time.ts +29 -0
- package/src/ui/mention-list.tsx +106 -0
- package/src/ui/mention-suggestion.test.ts +18 -0
- package/src/ui/mention-suggestion.tsx +285 -0
- package/src/ui/mention.tsx +9 -0
- package/src/ui/reaction.tsx +16 -0
- package/src/ui/reactions.tsx +15 -0
- package/src/ui/render-markdown.tsx +72 -0
- package/src/ui/send-button.tsx +15 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/canvas/state.ts"],
|
|
4
|
+
"sourcesContent": ["import {\n\tEditorAtom,\n\ttype BoxModel,\n\ttype Editor,\n\ttype TLHistoryBatchOptions,\n\tuseEditor,\n\tuseValue,\n} from 'tldraw'\nimport type { PendingComment } from './comment-tool'\nimport { getCommentingOptions } from './options'\nimport { DEFAULT_SIDEBAR_FILTERS, type SidebarFilters } from './sidebar-filters'\n\n/**\n * Transient commenting UI state, scoped per editor via {@link EditorAtom}. Editor-scoping (rather\n * than module-global atoms) keeps two editors on one page \u2014 or an editor and a second instance \u2014\n * from sharing open-thread, visibility, and filter state. Reachable from both React (`useEditor()`)\n * and the comment tool (`this.editor`).\n */\n\n/** The id of the one open thread (only one popover is open at a time), or null when all closed.\n * @public */\nexport const openThreadId = new EditorAtom<string | null>('openThreadId', () => null)\n\n/** The comment currently being placed (composer open, not yet posted), or null.\n * @public */\nexport const pendingComment = new EditorAtom<PendingComment | null>('pendingComment', () => null)\n\n/** The region rectangle being dragged out right now (page coords), or null when not dragging. The\n * comment tool writes it on each move; the overlay reads it to draw the live dashed box. */\nexport const regionDraft = new EditorAtom<BoxModel | null>('regionDraft', () => null)\n\n/**\n * Whether comment pins are hidden on the canvas. Governs the on-canvas layer (pins + open popover)\n * only \u2014 the sidebar is unaffected.\n * @public\n */\nexport const commentsHidden = new EditorAtom<boolean>('commentsHidden', () => false)\n\n/** Which threads the comments sidebar shows.\n * @public */\nexport const sidebarFilters = new EditorAtom<SidebarFilters>(\n\t'sidebarFilters',\n\t() => DEFAULT_SIDEBAR_FILTERS\n)\n\n/** Toggle comment-pin visibility for an editor.\n * @public */\nexport function toggleCommentsHidden(editor: Editor): void {\n\tcommentsHidden.update(editor, (hidden) => !hidden)\n}\n\n/** React hook for the open thread id.\n * @public */\nexport function useOpenThreadId(): string | null {\n\tconst editor = useEditor()\n\treturn useValue('open thread id', () => openThreadId.get(editor), [editor])\n}\n\n/** React hook for the pending (being-placed) comment.\n * @public */\nexport function usePendingComment(): PendingComment | null {\n\tconst editor = useEditor()\n\treturn useValue('pending comment', () => pendingComment.get(editor), [editor])\n}\n\n/** React hook for whether comment pins are hidden.\n * @public */\nexport function useCommentsHidden(): boolean {\n\tconst editor = useEditor()\n\treturn useValue('comments hidden', () => commentsHidden.get(editor), [editor])\n}\n\n/** React hook for the current sidebar filters.\n * @public */\nexport function useSidebarFilters(): SidebarFilters {\n\tconst editor = useEditor()\n\treturn useValue('sidebar filters', () => sidebarFilters.get(editor), [editor])\n}\n\n/**\n * Commit a comment mutation with the configured undo/redo behavior. All comment writes go through\n * here so the {@link CommentingOptions.history} option (and {@link CommentingOptions.dragHistory}\n * for pin re-anchors) governs whether they land on the undo stack. Defaults to `'ignore'`.\n * @public\n */\nexport function commitCommentMutation<T>(\n\teditor: Editor,\n\tfn: () => T,\n\tkind: 'mutation' | 'drag' = 'mutation'\n): T {\n\tconst options = getCommentingOptions(editor)\n\tconst history: TLHistoryBatchOptions['history'] =\n\t\tkind === 'drag' ? (options.dragHistory ?? options.history) : options.history\n\tlet result: T\n\teditor.run(\n\t\t() => {\n\t\t\tresult = fn()\n\t\t},\n\t\t{ history }\n\t)\n\treturn result!\n}\n"],
|
|
5
|
+
"mappings": "AAAA;AAAA,EACC;AAAA,EAIA;AAAA,EACA;AAAA,OACM;AAEP,SAAS,4BAA4B;AACrC,SAAS,+BAAoD;AAWtD,MAAM,eAAe,IAAI,WAA0B,gBAAgB,MAAM,IAAI;AAI7E,MAAM,iBAAiB,IAAI,WAAkC,kBAAkB,MAAM,IAAI;AAIzF,MAAM,cAAc,IAAI,WAA4B,eAAe,MAAM,IAAI;AAO7E,MAAM,iBAAiB,IAAI,WAAoB,kBAAkB,MAAM,KAAK;AAI5E,MAAM,iBAAiB,IAAI;AAAA,EACjC;AAAA,EACA,MAAM;AACP;AAIO,SAAS,qBAAqB,QAAsB;AAC1D,iBAAe,OAAO,QAAQ,CAAC,WAAW,CAAC,MAAM;AAClD;AAIO,SAAS,kBAAiC;AAChD,QAAM,SAAS,UAAU;AACzB,SAAO,SAAS,kBAAkB,MAAM,aAAa,IAAI,MAAM,GAAG,CAAC,MAAM,CAAC;AAC3E;AAIO,SAAS,oBAA2C;AAC1D,QAAM,SAAS,UAAU;AACzB,SAAO,SAAS,mBAAmB,MAAM,eAAe,IAAI,MAAM,GAAG,CAAC,MAAM,CAAC;AAC9E;AAIO,SAAS,oBAA6B;AAC5C,QAAM,SAAS,UAAU;AACzB,SAAO,SAAS,mBAAmB,MAAM,eAAe,IAAI,MAAM,GAAG,CAAC,MAAM,CAAC;AAC9E;AAIO,SAAS,oBAAoC;AACnD,QAAM,SAAS,UAAU;AACzB,SAAO,SAAS,mBAAmB,MAAM,eAAe,IAAI,MAAM,GAAG,CAAC,MAAM,CAAC;AAC9E;AAQO,SAAS,sBACf,QACA,IACA,OAA4B,YACxB;AACJ,QAAM,UAAU,qBAAqB,MAAM;AAC3C,QAAM,UACL,SAAS,SAAU,QAAQ,eAAe,QAAQ,UAAW,QAAQ;AACtE,MAAI;AACJ,SAAO;AAAA,IACN,MAAM;AACL,eAAS,GAAG;AAAA,IACb;AAAA,IACA,EAAE,QAAQ;AAAA,EACX;AACA,SAAO;AACR;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getRegionCommentOptions } from "./region-options.mjs";
|
|
2
|
+
import { openThreadId } from "./state.mjs";
|
|
3
|
+
const DEFAULT_IMPRECISE_SHAPE_ANCHOR = { x: 1, y: 0 };
|
|
4
|
+
const REGION_PIN_CORNER = { x: 1, y: 1 };
|
|
5
|
+
function regionPinPoint(region, corner = REGION_PIN_CORNER) {
|
|
6
|
+
return {
|
|
7
|
+
x: region.x + corner.x * region.w,
|
|
8
|
+
y: region.y + corner.y * region.h
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function anchorPagePoint(editor, anchor, impreciseShapeAnchor = DEFAULT_IMPRECISE_SHAPE_ANCHOR) {
|
|
12
|
+
switch (anchor.type) {
|
|
13
|
+
case "shape": {
|
|
14
|
+
const bounds = editor.getShapePageBounds(anchor.shapeId);
|
|
15
|
+
if (!bounds) return null;
|
|
16
|
+
const { x, y } = anchor.isPrecise ? anchor : impreciseShapeAnchor;
|
|
17
|
+
return { x: bounds.minX + x * bounds.w, y: bounds.minY + y * bounds.h };
|
|
18
|
+
}
|
|
19
|
+
case "text-range": {
|
|
20
|
+
const bounds = editor.getShapePageBounds(anchor.shapeId);
|
|
21
|
+
if (!bounds) return null;
|
|
22
|
+
return { x: bounds.maxX, y: bounds.minY };
|
|
23
|
+
}
|
|
24
|
+
case "point":
|
|
25
|
+
return { x: anchor.x, y: anchor.y };
|
|
26
|
+
case "region":
|
|
27
|
+
return regionPinPoint(anchor, getRegionCommentOptions(editor).pinCorner);
|
|
28
|
+
case "page":
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function shapeAnchorAt(editor, shapeId, page, precise) {
|
|
33
|
+
const bounds = editor.getShapePageBounds(shapeId);
|
|
34
|
+
if (!bounds || bounds.w === 0 || bounds.h === 0) {
|
|
35
|
+
return { type: "shape", shapeId, x: 0.5, y: 0.5, isPrecise: precise };
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
type: "shape",
|
|
39
|
+
shapeId,
|
|
40
|
+
x: (page.x - bounds.minX) / bounds.w,
|
|
41
|
+
y: (page.y - bounds.minY) / bounds.h,
|
|
42
|
+
isPrecise: precise
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function focusThread(editor, thread, impreciseShapeAnchor) {
|
|
46
|
+
if (thread.pageId !== editor.getCurrentPageId()) {
|
|
47
|
+
editor.setCurrentPage(thread.pageId);
|
|
48
|
+
}
|
|
49
|
+
openThreadId.set(editor, thread.id);
|
|
50
|
+
const point = anchorPagePoint(editor, thread.anchor, impreciseShapeAnchor);
|
|
51
|
+
if (point) editor.centerOnPoint(point, { animation: { duration: 200 } });
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
DEFAULT_IMPRECISE_SHAPE_ANCHOR,
|
|
55
|
+
REGION_PIN_CORNER,
|
|
56
|
+
anchorPagePoint,
|
|
57
|
+
focusThread,
|
|
58
|
+
regionPinPoint,
|
|
59
|
+
shapeAnchorAt
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=thread-state.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/canvas/thread-state.ts"],
|
|
4
|
+
"sourcesContent": ["import { BoxModel, Editor, TLCommentAnchor, TLCommentThread, TLShapeId, VecLike } from 'tldraw'\nimport { getRegionCommentOptions } from './region-options'\nimport { openThreadId } from './state'\n\n/** Where an imprecise shape comment sits by default: the shape's top-right corner. Overridable. @public */\nexport const DEFAULT_IMPRECISE_SHAPE_ANCHOR = { x: 1, y: 0 }\n\n/** The default corner a region's pin and composer sit on, as a normalized 0\u20131 offset (bottom-right).\n * Overridable per editor via region options; pin position, composer placement, region move, and\n * which corner has no resize handle all derive from the chosen corner. */\nexport const REGION_PIN_CORNER: VecLike = { x: 1, y: 1 }\n\n/** The page point of a region's pin corner. */\nexport function regionPinPoint(region: BoxModel, corner: VecLike = REGION_PIN_CORNER): VecLike {\n\treturn {\n\t\tx: region.x + corner.x * region.w,\n\t\ty: region.y + corner.y * region.h,\n\t}\n}\n\n/**\n * Where a thread's pin sits on the page, for each anchor kind. Null hides the pin. For imprecise\n * shape anchors the pin uses `impreciseShapeAnchor` (a normalized 0\u20131 spot, top-right by default)\n * rather than the stored `x`/`y`.\n * @public\n */\nexport function anchorPagePoint(\n\teditor: Editor,\n\tanchor: TLCommentAnchor,\n\timpreciseShapeAnchor: { x: number; y: number } = DEFAULT_IMPRECISE_SHAPE_ANCHOR\n): { x: number; y: number } | null {\n\tswitch (anchor.type) {\n\t\tcase 'shape': {\n\t\t\tconst bounds = editor.getShapePageBounds(anchor.shapeId as TLShapeId)\n\t\t\tif (!bounds) return null\n\t\t\t// Precise pins sit at their stored x/y; imprecise ones at the consumer's default spot.\n\t\t\tconst { x, y } = anchor.isPrecise ? anchor : impreciseShapeAnchor\n\t\t\treturn { x: bounds.minX + x * bounds.w, y: bounds.minY + y * bounds.h }\n\t\t}\n\t\tcase 'text-range': {\n\t\t\tconst bounds = editor.getShapePageBounds(anchor.shapeId as TLShapeId)\n\t\t\tif (!bounds) return null\n\t\t\treturn { x: bounds.maxX, y: bounds.minY }\n\t\t}\n\t\tcase 'point':\n\t\t\treturn { x: anchor.x, y: anchor.y }\n\t\tcase 'region':\n\t\t\treturn regionPinPoint(anchor, getRegionCommentOptions(editor).pinCorner)\n\t\tcase 'page':\n\t\t\treturn null\n\t}\n}\n\n/**\n * A shape anchor for a page point. `x`/`y` are the point's normalized (0\u20131) offset within the\n * shape's page bounds, remembered either way. When `precise` (Alt held) the pin sits at exactly\n * `x`/`y`; otherwise it sits at the consumer's imprecise default (top-right out of the box).\n * @public\n */\nexport function shapeAnchorAt(\n\teditor: Editor,\n\tshapeId: TLShapeId,\n\tpage: { x: number; y: number },\n\tprecise: boolean\n): TLCommentAnchor {\n\tconst bounds = editor.getShapePageBounds(shapeId)\n\tif (!bounds || bounds.w === 0 || bounds.h === 0) {\n\t\treturn { type: 'shape', shapeId, x: 0.5, y: 0.5, isPrecise: precise }\n\t}\n\treturn {\n\t\ttype: 'shape',\n\t\tshapeId,\n\t\tx: (page.x - bounds.minX) / bounds.w,\n\t\ty: (page.y - bounds.minY) / bounds.h,\n\t\tisPrecise: precise,\n\t}\n}\n\n/** Open a thread and bring it into view \u2014 switch to its page if needed, then center its pin. @public */\nexport function focusThread(\n\teditor: Editor,\n\tthread: TLCommentThread,\n\timpreciseShapeAnchor?: { x: number; y: number }\n): void {\n\tif (thread.pageId !== editor.getCurrentPageId()) {\n\t\teditor.setCurrentPage(thread.pageId as any)\n\t}\n\topenThreadId.set(editor, thread.id)\n\tconst point = anchorPagePoint(editor, thread.anchor, impreciseShapeAnchor)\n\tif (point) editor.centerOnPoint(point, { animation: { duration: 200 } })\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,+BAA+B;AACxC,SAAS,oBAAoB;AAGtB,MAAM,iCAAiC,EAAE,GAAG,GAAG,GAAG,EAAE;AAKpD,MAAM,oBAA6B,EAAE,GAAG,GAAG,GAAG,EAAE;AAGhD,SAAS,eAAe,QAAkB,SAAkB,mBAA4B;AAC9F,SAAO;AAAA,IACN,GAAG,OAAO,IAAI,OAAO,IAAI,OAAO;AAAA,IAChC,GAAG,OAAO,IAAI,OAAO,IAAI,OAAO;AAAA,EACjC;AACD;AAQO,SAAS,gBACf,QACA,QACA,uBAAiD,gCACf;AAClC,UAAQ,OAAO,MAAM;AAAA,IACpB,KAAK,SAAS;AACb,YAAM,SAAS,OAAO,mBAAmB,OAAO,OAAoB;AACpE,UAAI,CAAC,OAAQ,QAAO;AAEpB,YAAM,EAAE,GAAG,EAAE,IAAI,OAAO,YAAY,SAAS;AAC7C,aAAO,EAAE,GAAG,OAAO,OAAO,IAAI,OAAO,GAAG,GAAG,OAAO,OAAO,IAAI,OAAO,EAAE;AAAA,IACvE;AAAA,IACA,KAAK,cAAc;AAClB,YAAM,SAAS,OAAO,mBAAmB,OAAO,OAAoB;AACpE,UAAI,CAAC,OAAQ,QAAO;AACpB,aAAO,EAAE,GAAG,OAAO,MAAM,GAAG,OAAO,KAAK;AAAA,IACzC;AAAA,IACA,KAAK;AACJ,aAAO,EAAE,GAAG,OAAO,GAAG,GAAG,OAAO,EAAE;AAAA,IACnC,KAAK;AACJ,aAAO,eAAe,QAAQ,wBAAwB,MAAM,EAAE,SAAS;AAAA,IACxE,KAAK;AACJ,aAAO;AAAA,EACT;AACD;AAQO,SAAS,cACf,QACA,SACA,MACA,SACkB;AAClB,QAAM,SAAS,OAAO,mBAAmB,OAAO;AAChD,MAAI,CAAC,UAAU,OAAO,MAAM,KAAK,OAAO,MAAM,GAAG;AAChD,WAAO,EAAE,MAAM,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK,WAAW,QAAQ;AAAA,EACrE;AACA,SAAO;AAAA,IACN,MAAM;AAAA,IACN;AAAA,IACA,IAAI,KAAK,IAAI,OAAO,QAAQ,OAAO;AAAA,IACnC,IAAI,KAAK,IAAI,OAAO,QAAQ,OAAO;AAAA,IACnC,WAAW;AAAA,EACZ;AACD;AAGO,SAAS,YACf,QACA,QACA,sBACO;AACP,MAAI,OAAO,WAAW,OAAO,iBAAiB,GAAG;AAChD,WAAO,eAAe,OAAO,MAAa;AAAA,EAC3C;AACA,eAAa,IAAI,QAAQ,OAAO,EAAE;AAClC,QAAM,QAAQ,gBAAgB,QAAQ,OAAO,QAAQ,oBAAoB;AACzE,MAAI,MAAO,QAAO,cAAc,OAAO,EAAE,WAAW,EAAE,UAAU,IAAI,EAAE,CAAC;AACxE;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { mstEdges } from "./mst.mjs";
|
|
2
|
+
import { cappedReplay } from "./replay.mjs";
|
|
3
|
+
import { contract, finalize } from "./schedule.mjs";
|
|
4
|
+
function computeClusterTable(leaves, options) {
|
|
5
|
+
const opts = resolveOptions(options);
|
|
6
|
+
const leafNodes = leaves.map(leafToNode);
|
|
7
|
+
const edges = mstEdges(leaves);
|
|
8
|
+
if (leaves.length < 2) {
|
|
9
|
+
return { events: [], leaves: leafNodes };
|
|
10
|
+
}
|
|
11
|
+
const raw = cappedReplay(leaves, edges, { Tc: opts.Tc, Dmax: opts.Dmax });
|
|
12
|
+
const contracted = contract(raw, opts.eps);
|
|
13
|
+
const events = finalize(contracted, {
|
|
14
|
+
Tc: opts.Tc,
|
|
15
|
+
Tu: opts.Tu,
|
|
16
|
+
minZoom: opts.minZoom,
|
|
17
|
+
maxZoom: opts.maxZoom,
|
|
18
|
+
maxSplitZoom: opts.maxSplitZoom
|
|
19
|
+
});
|
|
20
|
+
return { events, leaves: leafNodes };
|
|
21
|
+
}
|
|
22
|
+
function resolveOptions(options) {
|
|
23
|
+
const Tc = options.Tc ?? 40;
|
|
24
|
+
const Tu = options.Tu ?? 1.5 * Tc;
|
|
25
|
+
const eps = options.eps ?? 0.12;
|
|
26
|
+
const Dmax = options.Dmax ?? 3 * Tc;
|
|
27
|
+
const maxSplitZoom = options.maxSplitZoom ?? 6;
|
|
28
|
+
const { minZoom, maxZoom } = options;
|
|
29
|
+
if (!Number.isFinite(Tc) || Tc <= 0) {
|
|
30
|
+
throw new Error("Tc must be finite and greater than 0");
|
|
31
|
+
}
|
|
32
|
+
if (!Number.isFinite(Tu) || Tu <= Tc) {
|
|
33
|
+
throw new Error("Tu must be finite and greater than Tc");
|
|
34
|
+
}
|
|
35
|
+
if (!Number.isFinite(eps) || eps < 0) {
|
|
36
|
+
throw new Error("eps must be finite and greater than or equal to 0");
|
|
37
|
+
}
|
|
38
|
+
if (!Number.isFinite(Dmax) || Dmax < Tc) {
|
|
39
|
+
throw new Error("Dmax must be finite and greater than or equal to Tc");
|
|
40
|
+
}
|
|
41
|
+
if (!Number.isFinite(minZoom) || minZoom <= 0) {
|
|
42
|
+
throw new Error("minZoom must be finite and greater than 0");
|
|
43
|
+
}
|
|
44
|
+
if (!Number.isFinite(maxZoom) || maxZoom <= minZoom) {
|
|
45
|
+
throw new Error("maxZoom must be finite and greater than minZoom");
|
|
46
|
+
}
|
|
47
|
+
if (!Number.isFinite(maxSplitZoom) || maxSplitZoom <= 0) {
|
|
48
|
+
throw new Error("maxSplitZoom must be finite and greater than 0");
|
|
49
|
+
}
|
|
50
|
+
return { Tc, Tu, eps, Dmax, minZoom, maxZoom, maxSplitZoom };
|
|
51
|
+
}
|
|
52
|
+
function leafToNode(leaf) {
|
|
53
|
+
return {
|
|
54
|
+
id: leaf.id,
|
|
55
|
+
centroid: { x: leaf.point.x, y: leaf.point.y },
|
|
56
|
+
count: 1,
|
|
57
|
+
members: [leaf.id]
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
computeClusterTable
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=computeClusterTable.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/clustering/computeClusterTable.ts"],
|
|
4
|
+
"sourcesContent": ["import { mstEdges } from './mst'\nimport { cappedReplay } from './replay'\nimport { contract, finalize } from './schedule'\nimport type { ClusterNode, ClusterOptions, ClusterTable, LeafInput } from './types'\n\ninterface ResolvedClusterOptions {\n\tTc: number\n\tTu: number\n\teps: number\n\tDmax: number\n\tminZoom: number\n\tmaxZoom: number\n\tmaxSplitZoom: number\n}\n\n/** @public */\nexport function computeClusterTable(\n\tleaves: readonly LeafInput[],\n\toptions: ClusterOptions\n): ClusterTable {\n\tconst opts = resolveOptions(options)\n\tconst leafNodes = leaves.map(leafToNode)\n\tconst edges = mstEdges(leaves)\n\n\tif (leaves.length < 2) {\n\t\treturn { events: [], leaves: leafNodes }\n\t}\n\n\tconst raw = cappedReplay(leaves, edges, { Tc: opts.Tc, Dmax: opts.Dmax })\n\tconst contracted = contract(raw, opts.eps)\n\tconst events = finalize(contracted, {\n\t\tTc: opts.Tc,\n\t\tTu: opts.Tu,\n\t\tminZoom: opts.minZoom,\n\t\tmaxZoom: opts.maxZoom,\n\t\tmaxSplitZoom: opts.maxSplitZoom,\n\t})\n\n\treturn { events, leaves: leafNodes }\n}\n\nfunction resolveOptions(options: ClusterOptions): ResolvedClusterOptions {\n\tconst Tc = options.Tc ?? 40\n\tconst Tu = options.Tu ?? 1.5 * Tc\n\tconst eps = options.eps ?? 0.12\n\tconst Dmax = options.Dmax ?? 3 * Tc\n\tconst maxSplitZoom = options.maxSplitZoom ?? 6\n\tconst { minZoom, maxZoom } = options\n\n\tif (!Number.isFinite(Tc) || Tc <= 0) {\n\t\tthrow new Error('Tc must be finite and greater than 0')\n\t}\n\tif (!Number.isFinite(Tu) || Tu <= Tc) {\n\t\tthrow new Error('Tu must be finite and greater than Tc')\n\t}\n\tif (!Number.isFinite(eps) || eps < 0) {\n\t\tthrow new Error('eps must be finite and greater than or equal to 0')\n\t}\n\tif (!Number.isFinite(Dmax) || Dmax < Tc) {\n\t\tthrow new Error('Dmax must be finite and greater than or equal to Tc')\n\t}\n\tif (!Number.isFinite(minZoom) || minZoom <= 0) {\n\t\tthrow new Error('minZoom must be finite and greater than 0')\n\t}\n\tif (!Number.isFinite(maxZoom) || maxZoom <= minZoom) {\n\t\tthrow new Error('maxZoom must be finite and greater than minZoom')\n\t}\n\tif (!Number.isFinite(maxSplitZoom) || maxSplitZoom <= 0) {\n\t\tthrow new Error('maxSplitZoom must be finite and greater than 0')\n\t}\n\n\treturn { Tc, Tu, eps, Dmax, minZoom, maxZoom, maxSplitZoom }\n}\n\nfunction leafToNode(leaf: LeafInput): ClusterNode {\n\treturn {\n\t\tid: leaf.id,\n\t\tcentroid: { x: leaf.point.x, y: leaf.point.y },\n\t\tcount: 1,\n\t\tmembers: [leaf.id],\n\t}\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,oBAAoB;AAC7B,SAAS,UAAU,gBAAgB;AAc5B,SAAS,oBACf,QACA,SACe;AACf,QAAM,OAAO,eAAe,OAAO;AACnC,QAAM,YAAY,OAAO,IAAI,UAAU;AACvC,QAAM,QAAQ,SAAS,MAAM;AAE7B,MAAI,OAAO,SAAS,GAAG;AACtB,WAAO,EAAE,QAAQ,CAAC,GAAG,QAAQ,UAAU;AAAA,EACxC;AAEA,QAAM,MAAM,aAAa,QAAQ,OAAO,EAAE,IAAI,KAAK,IAAI,MAAM,KAAK,KAAK,CAAC;AACxE,QAAM,aAAa,SAAS,KAAK,KAAK,GAAG;AACzC,QAAM,SAAS,SAAS,YAAY;AAAA,IACnC,IAAI,KAAK;AAAA,IACT,IAAI,KAAK;AAAA,IACT,SAAS,KAAK;AAAA,IACd,SAAS,KAAK;AAAA,IACd,cAAc,KAAK;AAAA,EACpB,CAAC;AAED,SAAO,EAAE,QAAQ,QAAQ,UAAU;AACpC;AAEA,SAAS,eAAe,SAAiD;AACxE,QAAM,KAAK,QAAQ,MAAM;AACzB,QAAM,KAAK,QAAQ,MAAM,MAAM;AAC/B,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,OAAO,QAAQ,QAAQ,IAAI;AACjC,QAAM,eAAe,QAAQ,gBAAgB;AAC7C,QAAM,EAAE,SAAS,QAAQ,IAAI;AAE7B,MAAI,CAAC,OAAO,SAAS,EAAE,KAAK,MAAM,GAAG;AACpC,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACvD;AACA,MAAI,CAAC,OAAO,SAAS,EAAE,KAAK,MAAM,IAAI;AACrC,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACxD;AACA,MAAI,CAAC,OAAO,SAAS,GAAG,KAAK,MAAM,GAAG;AACrC,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACpE;AACA,MAAI,CAAC,OAAO,SAAS,IAAI,KAAK,OAAO,IAAI;AACxC,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACtE;AACA,MAAI,CAAC,OAAO,SAAS,OAAO,KAAK,WAAW,GAAG;AAC9C,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC5D;AACA,MAAI,CAAC,OAAO,SAAS,OAAO,KAAK,WAAW,SAAS;AACpD,UAAM,IAAI,MAAM,iDAAiD;AAAA,EAClE;AACA,MAAI,CAAC,OAAO,SAAS,YAAY,KAAK,gBAAgB,GAAG;AACxD,UAAM,IAAI,MAAM,gDAAgD;AAAA,EACjE;AAEA,SAAO,EAAE,IAAI,IAAI,KAAK,MAAM,SAAS,SAAS,aAAa;AAC5D;AAEA,SAAS,WAAW,MAA8B;AACjD,SAAO;AAAA,IACN,IAAI,KAAK;AAAA,IACT,UAAU,EAAE,GAAG,KAAK,MAAM,GAAG,GAAG,KAAK,MAAM,EAAE;AAAA,IAC7C,OAAO;AAAA,IACP,SAAS,CAAC,KAAK,EAAE;AAAA,EAClB;AACD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
function mstEdges(leaves) {
|
|
2
|
+
const n = leaves.length;
|
|
3
|
+
const ids = new Array(n);
|
|
4
|
+
const xs = new Float64Array(n);
|
|
5
|
+
const ys = new Float64Array(n);
|
|
6
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7
|
+
let root = 0;
|
|
8
|
+
for (let i = 0; i < n; i++) {
|
|
9
|
+
const leaf = leaves[i];
|
|
10
|
+
if (seen.has(leaf.id)) {
|
|
11
|
+
throw new Error(`Duplicate leaf id: ${leaf.id}`);
|
|
12
|
+
}
|
|
13
|
+
seen.add(leaf.id);
|
|
14
|
+
if (!Number.isFinite(leaf.point.x) || !Number.isFinite(leaf.point.y)) {
|
|
15
|
+
throw new Error(`Non-finite coordinate for leaf id: ${leaf.id}`);
|
|
16
|
+
}
|
|
17
|
+
ids[i] = leaf.id;
|
|
18
|
+
xs[i] = leaf.point.x;
|
|
19
|
+
ys[i] = leaf.point.y;
|
|
20
|
+
if (i > 0 && leaf.id < ids[root]) root = i;
|
|
21
|
+
}
|
|
22
|
+
if (n < 2) return [];
|
|
23
|
+
const inTree = new Uint8Array(n);
|
|
24
|
+
const bestD2 = new Float64Array(n);
|
|
25
|
+
const bestFrom = new Int32Array(n);
|
|
26
|
+
inTree[root] = 1;
|
|
27
|
+
bestD2.fill(Number.POSITIVE_INFINITY);
|
|
28
|
+
bestFrom.fill(-1);
|
|
29
|
+
for (let i = 0; i < n; i++) {
|
|
30
|
+
if (i === root) continue;
|
|
31
|
+
bestD2[i] = squaredDistance(xs, ys, root, i);
|
|
32
|
+
bestFrom[i] = root;
|
|
33
|
+
}
|
|
34
|
+
const edges = [];
|
|
35
|
+
for (let edgeCount = 0; edgeCount < n - 1; edgeCount++) {
|
|
36
|
+
let next = -1;
|
|
37
|
+
for (let i = 0; i < n; i++) {
|
|
38
|
+
if (inTree[i]) continue;
|
|
39
|
+
if (next === -1 || edgeKeyLess(bestD2[i], bestFrom[i], i, bestD2[next], bestFrom[next], next, ids)) {
|
|
40
|
+
next = i;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const from = bestFrom[next];
|
|
44
|
+
edges.push(createOutputEdge(xs, ys, ids, from, next));
|
|
45
|
+
inTree[next] = 1;
|
|
46
|
+
for (let i = 0; i < n; i++) {
|
|
47
|
+
if (inTree[i]) continue;
|
|
48
|
+
const d2 = squaredDistance(xs, ys, next, i);
|
|
49
|
+
if (edgeKeyLess(d2, next, i, bestD2[i], bestFrom[i], i, ids)) {
|
|
50
|
+
bestD2[i] = d2;
|
|
51
|
+
bestFrom[i] = next;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
edges.sort((a, b) => outputEdgeCompare(a, b, leaves));
|
|
56
|
+
return edges;
|
|
57
|
+
}
|
|
58
|
+
function squaredDistance(xs, ys, a, b) {
|
|
59
|
+
const dx = xs[a] - xs[b];
|
|
60
|
+
const dy = ys[a] - ys[b];
|
|
61
|
+
return dx * dx + dy * dy;
|
|
62
|
+
}
|
|
63
|
+
function edgeKeyLess(d2A, a0, a1, d2B, b0, b1, ids) {
|
|
64
|
+
if (d2A !== d2B) return d2A < d2B;
|
|
65
|
+
const aLo = ids[a0] < ids[a1] ? ids[a0] : ids[a1];
|
|
66
|
+
const aHi = ids[a0] < ids[a1] ? ids[a1] : ids[a0];
|
|
67
|
+
const bLo = ids[b0] < ids[b1] ? ids[b0] : ids[b1];
|
|
68
|
+
const bHi = ids[b0] < ids[b1] ? ids[b1] : ids[b0];
|
|
69
|
+
if (aLo !== bLo) return aLo < bLo;
|
|
70
|
+
return aHi < bHi;
|
|
71
|
+
}
|
|
72
|
+
function createOutputEdge(xs, ys, ids, i, j) {
|
|
73
|
+
const a = ids[i] < ids[j] ? i : j;
|
|
74
|
+
const b = ids[i] < ids[j] ? j : i;
|
|
75
|
+
return {
|
|
76
|
+
a,
|
|
77
|
+
b,
|
|
78
|
+
d: Math.hypot(xs[a] - xs[b], ys[a] - ys[b])
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function outputEdgeCompare(a, b, leaves) {
|
|
82
|
+
if (a.d < b.d) return -1;
|
|
83
|
+
if (a.d > b.d) return 1;
|
|
84
|
+
const aLo = leaves[a.a].id;
|
|
85
|
+
const aHi = leaves[a.b].id;
|
|
86
|
+
const bLo = leaves[b.a].id;
|
|
87
|
+
const bHi = leaves[b.b].id;
|
|
88
|
+
if (aLo < bLo) return -1;
|
|
89
|
+
if (aLo > bLo) return 1;
|
|
90
|
+
if (aHi < bHi) return -1;
|
|
91
|
+
if (aHi > bHi) return 1;
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
export {
|
|
95
|
+
mstEdges
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=mst.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/clustering/mst.ts"],
|
|
4
|
+
"sourcesContent": ["import type { LeafInput, MstEdge } from './types'\n\n/**\n * Return the Euclidean MST over the leaves, ordered by the canonical edge key:\n * distance, then normalized endpoint ids.\n */\nexport function mstEdges(leaves: readonly LeafInput[]): MstEdge[] {\n\tconst n = leaves.length\n\tconst ids = new Array<string>(n)\n\tconst xs = new Float64Array(n)\n\tconst ys = new Float64Array(n)\n\tconst seen = new Set<string>()\n\n\tlet root = 0\n\tfor (let i = 0; i < n; i++) {\n\t\tconst leaf = leaves[i]\n\t\tif (seen.has(leaf.id)) {\n\t\t\tthrow new Error(`Duplicate leaf id: ${leaf.id}`)\n\t\t}\n\t\tseen.add(leaf.id)\n\t\tif (!Number.isFinite(leaf.point.x) || !Number.isFinite(leaf.point.y)) {\n\t\t\tthrow new Error(`Non-finite coordinate for leaf id: ${leaf.id}`)\n\t\t}\n\t\tids[i] = leaf.id\n\t\txs[i] = leaf.point.x\n\t\tys[i] = leaf.point.y\n\t\tif (i > 0 && leaf.id < ids[root]) root = i\n\t}\n\n\tif (n < 2) return []\n\n\tconst inTree = new Uint8Array(n)\n\tconst bestD2 = new Float64Array(n)\n\tconst bestFrom = new Int32Array(n)\n\n\tinTree[root] = 1\n\tbestD2.fill(Number.POSITIVE_INFINITY)\n\tbestFrom.fill(-1)\n\n\tfor (let i = 0; i < n; i++) {\n\t\tif (i === root) continue\n\t\tbestD2[i] = squaredDistance(xs, ys, root, i)\n\t\tbestFrom[i] = root\n\t}\n\n\tconst edges: MstEdge[] = []\n\n\tfor (let edgeCount = 0; edgeCount < n - 1; edgeCount++) {\n\t\tlet next = -1\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tif (inTree[i]) continue\n\t\t\tif (\n\t\t\t\tnext === -1 ||\n\t\t\t\tedgeKeyLess(bestD2[i], bestFrom[i], i, bestD2[next], bestFrom[next], next, ids)\n\t\t\t) {\n\t\t\t\tnext = i\n\t\t\t}\n\t\t}\n\n\t\tconst from = bestFrom[next]\n\t\tedges.push(createOutputEdge(xs, ys, ids, from, next))\n\t\tinTree[next] = 1\n\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tif (inTree[i]) continue\n\t\t\tconst d2 = squaredDistance(xs, ys, next, i)\n\t\t\tif (edgeKeyLess(d2, next, i, bestD2[i], bestFrom[i], i, ids)) {\n\t\t\t\tbestD2[i] = d2\n\t\t\t\tbestFrom[i] = next\n\t\t\t}\n\t\t}\n\t}\n\n\tedges.sort((a, b) => outputEdgeCompare(a, b, leaves))\n\treturn edges\n}\n\nfunction squaredDistance(xs: Float64Array, ys: Float64Array, a: number, b: number): number {\n\tconst dx = xs[a] - xs[b]\n\tconst dy = ys[a] - ys[b]\n\treturn dx * dx + dy * dy\n}\n\nfunction edgeKeyLess(\n\td2A: number,\n\ta0: number,\n\ta1: number,\n\td2B: number,\n\tb0: number,\n\tb1: number,\n\tids: readonly string[]\n): boolean {\n\tif (d2A !== d2B) return d2A < d2B\n\n\tconst aLo = ids[a0] < ids[a1] ? ids[a0] : ids[a1]\n\tconst aHi = ids[a0] < ids[a1] ? ids[a1] : ids[a0]\n\tconst bLo = ids[b0] < ids[b1] ? ids[b0] : ids[b1]\n\tconst bHi = ids[b0] < ids[b1] ? ids[b1] : ids[b0]\n\n\tif (aLo !== bLo) return aLo < bLo\n\treturn aHi < bHi\n}\n\nfunction createOutputEdge(\n\txs: Float64Array,\n\tys: Float64Array,\n\tids: readonly string[],\n\ti: number,\n\tj: number\n): MstEdge {\n\tconst a = ids[i] < ids[j] ? i : j\n\tconst b = ids[i] < ids[j] ? j : i\n\treturn {\n\t\ta,\n\t\tb,\n\t\td: Math.hypot(xs[a] - xs[b], ys[a] - ys[b]),\n\t}\n}\n\nfunction outputEdgeCompare(a: MstEdge, b: MstEdge, leaves: readonly LeafInput[]): number {\n\tif (a.d < b.d) return -1\n\tif (a.d > b.d) return 1\n\n\tconst aLo = leaves[a.a].id\n\tconst aHi = leaves[a.b].id\n\tconst bLo = leaves[b.a].id\n\tconst bHi = leaves[b.b].id\n\n\tif (aLo < bLo) return -1\n\tif (aLo > bLo) return 1\n\tif (aHi < bHi) return -1\n\tif (aHi > bHi) return 1\n\treturn 0\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
const D_FLOOR = 1e-9;
|
|
2
|
+
function cappedReplay(leaves, edges, opts) {
|
|
3
|
+
validateOptions(opts);
|
|
4
|
+
if (edges.length === 0) return [];
|
|
5
|
+
const clusters = new ClusterState(leaves);
|
|
6
|
+
const heap = new EdgeMaxHeap(edges, leaves);
|
|
7
|
+
const incident = leaves.map(() => []);
|
|
8
|
+
for (let i = 0; i < edges.length; i++) {
|
|
9
|
+
heap.push({ edgeIndex: i, z: zForEdge(edges[i], clusters, opts) });
|
|
10
|
+
incident[edges[i].a].push(i);
|
|
11
|
+
incident[edges[i].b].push(i);
|
|
12
|
+
}
|
|
13
|
+
const events = [];
|
|
14
|
+
let lastZ = Number.POSITIVE_INFINITY;
|
|
15
|
+
while (events.length < edges.length) {
|
|
16
|
+
const entry = heap.pop();
|
|
17
|
+
if (!entry) break;
|
|
18
|
+
const edge = edges[entry.edgeIndex];
|
|
19
|
+
const aRoot = clusters.find(edge.a);
|
|
20
|
+
const bRoot = clusters.find(edge.b);
|
|
21
|
+
if (aRoot === bRoot) continue;
|
|
22
|
+
const current = { edgeIndex: entry.edgeIndex, z: zForRoots(edge, aRoot, bRoot, clusters, opts) };
|
|
23
|
+
const next = heap.peek();
|
|
24
|
+
if (next && heap.higherPriority(next, current)) {
|
|
25
|
+
heap.push(current);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const z = Math.min(current.z, lastZ);
|
|
29
|
+
lastZ = z;
|
|
30
|
+
events.push(clusters.merge(aRoot, bRoot, z));
|
|
31
|
+
const root = clusters.find(edge.a);
|
|
32
|
+
const other = root === aRoot ? bRoot : aRoot;
|
|
33
|
+
const survivors = [];
|
|
34
|
+
for (const idx of [...incident[root], ...incident[other]]) {
|
|
35
|
+
const e2 = edges[idx];
|
|
36
|
+
const ra = clusters.find(e2.a);
|
|
37
|
+
const rb = clusters.find(e2.b);
|
|
38
|
+
if (ra === rb) continue;
|
|
39
|
+
survivors.push(idx);
|
|
40
|
+
heap.push({ edgeIndex: idx, z: zForRoots(e2, ra, rb, clusters, opts) });
|
|
41
|
+
}
|
|
42
|
+
incident[root] = survivors;
|
|
43
|
+
}
|
|
44
|
+
return events;
|
|
45
|
+
}
|
|
46
|
+
function validateOptions(opts) {
|
|
47
|
+
if (!Number.isFinite(opts.Tc) || opts.Tc <= 0) {
|
|
48
|
+
throw new Error("Tc must be greater than 0");
|
|
49
|
+
}
|
|
50
|
+
if (!Number.isFinite(opts.Dmax) || opts.Dmax <= 0) {
|
|
51
|
+
throw new Error("Dmax must be greater than 0");
|
|
52
|
+
}
|
|
53
|
+
if (opts.Dmax < opts.Tc) {
|
|
54
|
+
throw new Error("Dmax must be greater than or equal to Tc");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function zForEdge(edge, clusters, opts) {
|
|
58
|
+
return zForRoots(edge, clusters.find(edge.a), clusters.find(edge.b), clusters, opts);
|
|
59
|
+
}
|
|
60
|
+
function zForRoots(edge, aRoot, bRoot, clusters, opts) {
|
|
61
|
+
if (edge.d < D_FLOOR) return Number.POSITIVE_INFINITY;
|
|
62
|
+
const gap = opts.Tc / clusters.centroidDistance(aRoot, bRoot);
|
|
63
|
+
const fit = opts.Dmax / clusters.unionBboxDiag(aRoot, bRoot);
|
|
64
|
+
return Math.min(gap, fit);
|
|
65
|
+
}
|
|
66
|
+
class ClusterState {
|
|
67
|
+
parent;
|
|
68
|
+
minX;
|
|
69
|
+
minY;
|
|
70
|
+
maxX;
|
|
71
|
+
maxY;
|
|
72
|
+
centroidX;
|
|
73
|
+
centroidY;
|
|
74
|
+
counts;
|
|
75
|
+
nodes;
|
|
76
|
+
memberLists;
|
|
77
|
+
minMemberIds;
|
|
78
|
+
constructor(leaves) {
|
|
79
|
+
const n = leaves.length;
|
|
80
|
+
this.parent = new Int32Array(n);
|
|
81
|
+
this.minX = new Float64Array(n);
|
|
82
|
+
this.minY = new Float64Array(n);
|
|
83
|
+
this.maxX = new Float64Array(n);
|
|
84
|
+
this.maxY = new Float64Array(n);
|
|
85
|
+
this.centroidX = new Float64Array(n);
|
|
86
|
+
this.centroidY = new Float64Array(n);
|
|
87
|
+
this.counts = new Int32Array(n);
|
|
88
|
+
this.nodes = new Array(n);
|
|
89
|
+
this.memberLists = new Array(n);
|
|
90
|
+
this.minMemberIds = new Array(n);
|
|
91
|
+
for (let i = 0; i < n; i++) {
|
|
92
|
+
const leaf = leaves[i];
|
|
93
|
+
this.parent[i] = i;
|
|
94
|
+
this.minX[i] = leaf.point.x;
|
|
95
|
+
this.minY[i] = leaf.point.y;
|
|
96
|
+
this.maxX[i] = leaf.point.x;
|
|
97
|
+
this.maxY[i] = leaf.point.y;
|
|
98
|
+
this.centroidX[i] = leaf.point.x;
|
|
99
|
+
this.centroidY[i] = leaf.point.y;
|
|
100
|
+
this.counts[i] = 1;
|
|
101
|
+
this.memberLists[i] = [leaf.id];
|
|
102
|
+
this.minMemberIds[i] = leaf.id;
|
|
103
|
+
this.nodes[i] = {
|
|
104
|
+
id: leaf.id,
|
|
105
|
+
centroid: { x: leaf.point.x, y: leaf.point.y },
|
|
106
|
+
count: 1,
|
|
107
|
+
members: [leaf.id]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
find(index) {
|
|
112
|
+
let root = index;
|
|
113
|
+
while (this.parent[root] !== root) {
|
|
114
|
+
root = this.parent[root];
|
|
115
|
+
}
|
|
116
|
+
while (this.parent[index] !== index) {
|
|
117
|
+
const next = this.parent[index];
|
|
118
|
+
this.parent[index] = root;
|
|
119
|
+
index = next;
|
|
120
|
+
}
|
|
121
|
+
return root;
|
|
122
|
+
}
|
|
123
|
+
centroidDistance(aRoot, bRoot) {
|
|
124
|
+
return Math.hypot(
|
|
125
|
+
this.centroidX[aRoot] - this.centroidX[bRoot],
|
|
126
|
+
this.centroidY[aRoot] - this.centroidY[bRoot]
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
unionBboxDiag(aRoot, bRoot) {
|
|
130
|
+
const minX = Math.min(this.minX[aRoot], this.minX[bRoot]);
|
|
131
|
+
const minY = Math.min(this.minY[aRoot], this.minY[bRoot]);
|
|
132
|
+
const maxX = Math.max(this.maxX[aRoot], this.maxX[bRoot]);
|
|
133
|
+
const maxY = Math.max(this.maxY[aRoot], this.maxY[bRoot]);
|
|
134
|
+
return Math.hypot(maxX - minX, maxY - minY);
|
|
135
|
+
}
|
|
136
|
+
merge(aRoot, bRoot, z) {
|
|
137
|
+
const leftRoot = this.minMemberIds[aRoot] < this.minMemberIds[bRoot] ? aRoot : bRoot;
|
|
138
|
+
const rightRoot = leftRoot === aRoot ? bRoot : aRoot;
|
|
139
|
+
const left = this.nodes[leftRoot];
|
|
140
|
+
const right = this.nodes[rightRoot];
|
|
141
|
+
const count = this.counts[leftRoot] + this.counts[rightRoot];
|
|
142
|
+
const members = mergeSortedMembers(this.memberLists[leftRoot], this.memberLists[rightRoot]);
|
|
143
|
+
const minX = Math.min(this.minX[leftRoot], this.minX[rightRoot]);
|
|
144
|
+
const minY = Math.min(this.minY[leftRoot], this.minY[rightRoot]);
|
|
145
|
+
const maxX = Math.max(this.maxX[leftRoot], this.maxX[rightRoot]);
|
|
146
|
+
const maxY = Math.max(this.maxY[leftRoot], this.maxY[rightRoot]);
|
|
147
|
+
const centroidX = (this.counts[leftRoot] * this.centroidX[leftRoot] + this.counts[rightRoot] * this.centroidX[rightRoot]) / count;
|
|
148
|
+
const centroidY = (this.counts[leftRoot] * this.centroidY[leftRoot] + this.counts[rightRoot] * this.centroidY[rightRoot]) / count;
|
|
149
|
+
const result = {
|
|
150
|
+
id: `cluster:${count}:${members[0]}`,
|
|
151
|
+
centroid: { x: centroidX, y: centroidY },
|
|
152
|
+
count,
|
|
153
|
+
members
|
|
154
|
+
};
|
|
155
|
+
this.parent[rightRoot] = leftRoot;
|
|
156
|
+
this.minX[leftRoot] = minX;
|
|
157
|
+
this.minY[leftRoot] = minY;
|
|
158
|
+
this.maxX[leftRoot] = maxX;
|
|
159
|
+
this.maxY[leftRoot] = maxY;
|
|
160
|
+
this.centroidX[leftRoot] = centroidX;
|
|
161
|
+
this.centroidY[leftRoot] = centroidY;
|
|
162
|
+
this.counts[leftRoot] = count;
|
|
163
|
+
this.nodes[leftRoot] = result;
|
|
164
|
+
this.memberLists[leftRoot] = members;
|
|
165
|
+
this.minMemberIds[leftRoot] = members[0];
|
|
166
|
+
return { z, children: [left, right], result };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function mergeSortedMembers(a, b) {
|
|
170
|
+
const out = new Array(a.length + b.length);
|
|
171
|
+
let i = 0;
|
|
172
|
+
let j = 0;
|
|
173
|
+
let k = 0;
|
|
174
|
+
while (i < a.length && j < b.length) {
|
|
175
|
+
if (a[i] < b[j]) {
|
|
176
|
+
out[k++] = a[i++];
|
|
177
|
+
} else {
|
|
178
|
+
out[k++] = b[j++];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
while (i < a.length) out[k++] = a[i++];
|
|
182
|
+
while (j < b.length) out[k++] = b[j++];
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
class EdgeMaxHeap {
|
|
186
|
+
constructor(edges, leaves) {
|
|
187
|
+
this.edges = edges;
|
|
188
|
+
this.leaves = leaves;
|
|
189
|
+
}
|
|
190
|
+
edges;
|
|
191
|
+
leaves;
|
|
192
|
+
items = [];
|
|
193
|
+
peek() {
|
|
194
|
+
return this.items[0];
|
|
195
|
+
}
|
|
196
|
+
push(entry) {
|
|
197
|
+
this.items.push(entry);
|
|
198
|
+
this.siftUp(this.items.length - 1);
|
|
199
|
+
}
|
|
200
|
+
pop() {
|
|
201
|
+
if (this.items.length === 0) return void 0;
|
|
202
|
+
const first = this.items[0];
|
|
203
|
+
const last = this.items.pop();
|
|
204
|
+
if (this.items.length > 0) {
|
|
205
|
+
this.items[0] = last;
|
|
206
|
+
this.siftDown(0);
|
|
207
|
+
}
|
|
208
|
+
return first;
|
|
209
|
+
}
|
|
210
|
+
higherPriority(a, b) {
|
|
211
|
+
if (a.z > b.z) return true;
|
|
212
|
+
if (a.z < b.z) return false;
|
|
213
|
+
return edgeIdPairLess(this.edges[a.edgeIndex], this.edges[b.edgeIndex], this.leaves);
|
|
214
|
+
}
|
|
215
|
+
siftUp(index) {
|
|
216
|
+
while (index > 0) {
|
|
217
|
+
const parent = index - 1 >> 1;
|
|
218
|
+
if (!this.higherPriority(this.items[index], this.items[parent])) break;
|
|
219
|
+
[this.items[index], this.items[parent]] = [this.items[parent], this.items[index]];
|
|
220
|
+
index = parent;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
siftDown(index) {
|
|
224
|
+
while (true) {
|
|
225
|
+
const left = index * 2 + 1;
|
|
226
|
+
const right = left + 1;
|
|
227
|
+
let best = index;
|
|
228
|
+
if (left < this.items.length && this.higherPriority(this.items[left], this.items[best])) {
|
|
229
|
+
best = left;
|
|
230
|
+
}
|
|
231
|
+
if (right < this.items.length && this.higherPriority(this.items[right], this.items[best])) {
|
|
232
|
+
best = right;
|
|
233
|
+
}
|
|
234
|
+
if (best === index) break;
|
|
235
|
+
[this.items[index], this.items[best]] = [this.items[best], this.items[index]];
|
|
236
|
+
index = best;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function edgeIdPairLess(a, b, leaves) {
|
|
241
|
+
const [aLo, aHi] = normalizedEdgeIds(a, leaves);
|
|
242
|
+
const [bLo, bHi] = normalizedEdgeIds(b, leaves);
|
|
243
|
+
if (aLo !== bLo) return aLo < bLo;
|
|
244
|
+
return aHi < bHi;
|
|
245
|
+
}
|
|
246
|
+
function normalizedEdgeIds(edge, leaves) {
|
|
247
|
+
const aId = leaves[edge.a].id;
|
|
248
|
+
const bId = leaves[edge.b].id;
|
|
249
|
+
return aId < bId ? [aId, bId] : [bId, aId];
|
|
250
|
+
}
|
|
251
|
+
export {
|
|
252
|
+
D_FLOOR,
|
|
253
|
+
cappedReplay
|
|
254
|
+
};
|
|
255
|
+
//# sourceMappingURL=replay.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/clustering/replay.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ClusterNode, LeafInput, MstEdge, RawMergeEvent } from './types'\n\nexport const D_FLOOR = 1e-9\n\nexport function cappedReplay(\n\tleaves: readonly LeafInput[],\n\tedges: readonly MstEdge[],\n\topts: { Tc: number; Dmax: number }\n): RawMergeEvent[] {\n\tvalidateOptions(opts)\n\n\tif (edges.length === 0) return []\n\n\tconst clusters = new ClusterState(leaves)\n\tconst heap = new EdgeMaxHeap(edges, leaves)\n\t// Edges incident to each current cluster root, for eager repricing: a merge\n\t// moves the result's centroid, which can RAISE an incident edge's key (the\n\t// centroid may move toward a neighbor), so the lazy pop-time recheck alone\n\t// is not enough \u2014 every price change pushes a fresh heap entry.\n\tconst incident: number[][] = leaves.map(() => [])\n\tfor (let i = 0; i < edges.length; i++) {\n\t\theap.push({ edgeIndex: i, z: zForEdge(edges[i], clusters, opts) })\n\t\tincident[edges[i].a].push(i)\n\t\tincident[edges[i].b].push(i)\n\t}\n\n\tconst events: RawMergeEvent[] = []\n\t// Emitted thresholds are clamped non-increasing: with centroid pricing a\n\t// later merge's raw price can in principle exceed an earlier one's, and the\n\t// table's descending sort is load-bearing for the runtime cursor.\n\tlet lastZ = Number.POSITIVE_INFINITY\n\twhile (events.length < edges.length) {\n\t\tconst entry = heap.pop()\n\t\tif (!entry) break\n\n\t\tconst edge = edges[entry.edgeIndex]\n\t\tconst aRoot = clusters.find(edge.a)\n\t\tconst bRoot = clusters.find(edge.b)\n\t\tif (aRoot === bRoot) continue // stale duplicate of an already-fired edge\n\n\t\tconst current = { edgeIndex: entry.edgeIndex, z: zForRoots(edge, aRoot, bRoot, clusters, opts) }\n\t\tconst next = heap.peek()\n\t\tif (next && heap.higherPriority(next, current)) {\n\t\t\theap.push(current)\n\t\t\tcontinue\n\t\t}\n\n\t\tconst z = Math.min(current.z, lastZ)\n\t\tlastZ = z\n\t\tevents.push(clusters.merge(aRoot, bRoot, z))\n\n\t\t// eager reprice: the new cluster's centroid and bbox changed, so every\n\t\t// surviving incident edge gets a fresh entry at its current price\n\t\tconst root = clusters.find(edge.a)\n\t\tconst other = root === aRoot ? bRoot : aRoot\n\t\tconst survivors: number[] = []\n\t\tfor (const idx of [...incident[root], ...incident[other]]) {\n\t\t\tconst e2 = edges[idx]\n\t\t\tconst ra = clusters.find(e2.a)\n\t\t\tconst rb = clusters.find(e2.b)\n\t\t\tif (ra === rb) continue\n\t\t\tsurvivors.push(idx)\n\t\t\theap.push({ edgeIndex: idx, z: zForRoots(e2, ra, rb, clusters, opts) })\n\t\t}\n\t\tincident[root] = survivors\n\t}\n\n\treturn events\n}\n\nfunction validateOptions(opts: { Tc: number; Dmax: number }) {\n\tif (!Number.isFinite(opts.Tc) || opts.Tc <= 0) {\n\t\tthrow new Error('Tc must be greater than 0')\n\t}\n\tif (!Number.isFinite(opts.Dmax) || opts.Dmax <= 0) {\n\t\tthrow new Error('Dmax must be greater than 0')\n\t}\n\tif (opts.Dmax < opts.Tc) {\n\t\tthrow new Error('Dmax must be greater than or equal to Tc')\n\t}\n}\n\nfunction zForEdge(\n\tedge: MstEdge,\n\tclusters: ClusterState,\n\topts: { Tc: number; Dmax: number }\n): number {\n\treturn zForRoots(edge, clusters.find(edge.a), clusters.find(edge.b), clusters, opts)\n}\n\nfunction zForRoots(\n\tedge: MstEdge,\n\taRoot: number,\n\tbRoot: number,\n\tclusters: ClusterState,\n\topts: { Tc: number; Dmax: number }\n): number {\n\tif (edge.d < D_FLOOR) return Number.POSITIVE_INFINITY\n\t// Badge-anchored gap pricing: clusters render as badges at their centroids,\n\t// so the merge is priced by the distance between the rendered centers, not\n\t// the nearest members. For leaves the two are identical; for clusters the\n\t// centroid distance is larger, so groups merge later than their closest\n\t// members would suggest \u2014 matching what the user actually sees.\n\t// (Coincident CENTROIDS with non-coincident members leave the gap term\n\t// Infinity and the fit term finite \u2014 the min stays finite, no special case.)\n\tconst gap = opts.Tc / clusters.centroidDistance(aRoot, bRoot)\n\tconst fit = opts.Dmax / clusters.unionBboxDiag(aRoot, bRoot)\n\treturn Math.min(gap, fit)\n}\n\nclass ClusterState {\n\tprivate readonly parent: Int32Array\n\tprivate readonly minX: Float64Array\n\tprivate readonly minY: Float64Array\n\tprivate readonly maxX: Float64Array\n\tprivate readonly maxY: Float64Array\n\tprivate readonly centroidX: Float64Array\n\tprivate readonly centroidY: Float64Array\n\tprivate readonly counts: Int32Array\n\tprivate readonly nodes: ClusterNode[]\n\tprivate readonly memberLists: string[][]\n\tprivate readonly minMemberIds: string[]\n\n\tconstructor(leaves: readonly LeafInput[]) {\n\t\tconst n = leaves.length\n\t\tthis.parent = new Int32Array(n)\n\t\tthis.minX = new Float64Array(n)\n\t\tthis.minY = new Float64Array(n)\n\t\tthis.maxX = new Float64Array(n)\n\t\tthis.maxY = new Float64Array(n)\n\t\tthis.centroidX = new Float64Array(n)\n\t\tthis.centroidY = new Float64Array(n)\n\t\tthis.counts = new Int32Array(n)\n\t\tthis.nodes = new Array(n)\n\t\tthis.memberLists = new Array(n)\n\t\tthis.minMemberIds = new Array(n)\n\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst leaf = leaves[i]\n\t\t\tthis.parent[i] = i\n\t\t\tthis.minX[i] = leaf.point.x\n\t\t\tthis.minY[i] = leaf.point.y\n\t\t\tthis.maxX[i] = leaf.point.x\n\t\t\tthis.maxY[i] = leaf.point.y\n\t\t\tthis.centroidX[i] = leaf.point.x\n\t\t\tthis.centroidY[i] = leaf.point.y\n\t\t\tthis.counts[i] = 1\n\t\t\tthis.memberLists[i] = [leaf.id]\n\t\t\tthis.minMemberIds[i] = leaf.id\n\t\t\tthis.nodes[i] = {\n\t\t\t\tid: leaf.id,\n\t\t\t\tcentroid: { x: leaf.point.x, y: leaf.point.y },\n\t\t\t\tcount: 1,\n\t\t\t\tmembers: [leaf.id],\n\t\t\t}\n\t\t}\n\t}\n\n\tfind(index: number): number {\n\t\tlet root = index\n\t\twhile (this.parent[root] !== root) {\n\t\t\troot = this.parent[root]\n\t\t}\n\t\twhile (this.parent[index] !== index) {\n\t\t\tconst next = this.parent[index]\n\t\t\tthis.parent[index] = root\n\t\t\tindex = next\n\t\t}\n\t\treturn root\n\t}\n\n\tcentroidDistance(aRoot: number, bRoot: number): number {\n\t\treturn Math.hypot(\n\t\t\tthis.centroidX[aRoot] - this.centroidX[bRoot],\n\t\t\tthis.centroidY[aRoot] - this.centroidY[bRoot]\n\t\t)\n\t}\n\n\tunionBboxDiag(aRoot: number, bRoot: number): number {\n\t\tconst minX = Math.min(this.minX[aRoot], this.minX[bRoot])\n\t\tconst minY = Math.min(this.minY[aRoot], this.minY[bRoot])\n\t\tconst maxX = Math.max(this.maxX[aRoot], this.maxX[bRoot])\n\t\tconst maxY = Math.max(this.maxY[aRoot], this.maxY[bRoot])\n\t\treturn Math.hypot(maxX - minX, maxY - minY)\n\t}\n\n\tmerge(aRoot: number, bRoot: number, z: number): RawMergeEvent {\n\t\tconst leftRoot = this.minMemberIds[aRoot] < this.minMemberIds[bRoot] ? aRoot : bRoot\n\t\tconst rightRoot = leftRoot === aRoot ? bRoot : aRoot\n\t\tconst left = this.nodes[leftRoot]\n\t\tconst right = this.nodes[rightRoot]\n\t\tconst count = this.counts[leftRoot] + this.counts[rightRoot]\n\t\tconst members = mergeSortedMembers(this.memberLists[leftRoot], this.memberLists[rightRoot])\n\n\t\tconst minX = Math.min(this.minX[leftRoot], this.minX[rightRoot])\n\t\tconst minY = Math.min(this.minY[leftRoot], this.minY[rightRoot])\n\t\tconst maxX = Math.max(this.maxX[leftRoot], this.maxX[rightRoot])\n\t\tconst maxY = Math.max(this.maxY[leftRoot], this.maxY[rightRoot])\n\t\tconst centroidX =\n\t\t\t(this.counts[leftRoot] * this.centroidX[leftRoot] +\n\t\t\t\tthis.counts[rightRoot] * this.centroidX[rightRoot]) /\n\t\t\tcount\n\t\tconst centroidY =\n\t\t\t(this.counts[leftRoot] * this.centroidY[leftRoot] +\n\t\t\t\tthis.counts[rightRoot] * this.centroidY[rightRoot]) /\n\t\t\tcount\n\n\t\t// Leaf ids are assumed not to start with `cluster:`; see the step 2 contract.\n\t\tconst result: ClusterNode = {\n\t\t\tid: `cluster:${count}:${members[0]}`,\n\t\t\tcentroid: { x: centroidX, y: centroidY },\n\t\t\tcount,\n\t\t\tmembers,\n\t\t}\n\n\t\tthis.parent[rightRoot] = leftRoot\n\t\tthis.minX[leftRoot] = minX\n\t\tthis.minY[leftRoot] = minY\n\t\tthis.maxX[leftRoot] = maxX\n\t\tthis.maxY[leftRoot] = maxY\n\t\tthis.centroidX[leftRoot] = centroidX\n\t\tthis.centroidY[leftRoot] = centroidY\n\t\tthis.counts[leftRoot] = count\n\t\tthis.nodes[leftRoot] = result\n\t\tthis.memberLists[leftRoot] = members\n\t\tthis.minMemberIds[leftRoot] = members[0]\n\n\t\treturn { z, children: [left, right], result }\n\t}\n}\n\nfunction mergeSortedMembers(a: readonly string[], b: readonly string[]): string[] {\n\tconst out = new Array<string>(a.length + b.length)\n\tlet i = 0\n\tlet j = 0\n\tlet k = 0\n\twhile (i < a.length && j < b.length) {\n\t\tif (a[i] < b[j]) {\n\t\t\tout[k++] = a[i++]\n\t\t} else {\n\t\t\tout[k++] = b[j++]\n\t\t}\n\t}\n\twhile (i < a.length) out[k++] = a[i++]\n\twhile (j < b.length) out[k++] = b[j++]\n\treturn out\n}\n\ninterface HeapEntry {\n\tedgeIndex: number\n\tz: number\n}\n\nclass EdgeMaxHeap {\n\tprivate readonly items: HeapEntry[] = []\n\n\tconstructor(\n\t\tprivate readonly edges: readonly MstEdge[],\n\t\tprivate readonly leaves: readonly LeafInput[]\n\t) {}\n\n\tpeek(): HeapEntry | undefined {\n\t\treturn this.items[0]\n\t}\n\n\tpush(entry: HeapEntry) {\n\t\tthis.items.push(entry)\n\t\tthis.siftUp(this.items.length - 1)\n\t}\n\n\tpop(): HeapEntry | undefined {\n\t\tif (this.items.length === 0) return undefined\n\t\tconst first = this.items[0]\n\t\tconst last = this.items.pop()!\n\t\tif (this.items.length > 0) {\n\t\t\tthis.items[0] = last\n\t\t\tthis.siftDown(0)\n\t\t}\n\t\treturn first\n\t}\n\n\thigherPriority(a: HeapEntry, b: HeapEntry): boolean {\n\t\tif (a.z > b.z) return true\n\t\tif (a.z < b.z) return false\n\t\treturn edgeIdPairLess(this.edges[a.edgeIndex], this.edges[b.edgeIndex], this.leaves)\n\t}\n\n\tprivate siftUp(index: number) {\n\t\twhile (index > 0) {\n\t\t\tconst parent = (index - 1) >> 1\n\t\t\tif (!this.higherPriority(this.items[index], this.items[parent])) break\n\t\t\t;[this.items[index], this.items[parent]] = [this.items[parent], this.items[index]]\n\t\t\tindex = parent\n\t\t}\n\t}\n\n\tprivate siftDown(index: number) {\n\t\twhile (true) {\n\t\t\tconst left = index * 2 + 1\n\t\t\tconst right = left + 1\n\t\t\tlet best = index\n\t\t\tif (left < this.items.length && this.higherPriority(this.items[left], this.items[best])) {\n\t\t\t\tbest = left\n\t\t\t}\n\t\t\tif (right < this.items.length && this.higherPriority(this.items[right], this.items[best])) {\n\t\t\t\tbest = right\n\t\t\t}\n\t\t\tif (best === index) break\n\t\t\t;[this.items[index], this.items[best]] = [this.items[best], this.items[index]]\n\t\t\tindex = best\n\t\t}\n\t}\n}\n\nfunction edgeIdPairLess(a: MstEdge, b: MstEdge, leaves: readonly LeafInput[]): boolean {\n\tconst [aLo, aHi] = normalizedEdgeIds(a, leaves)\n\tconst [bLo, bHi] = normalizedEdgeIds(b, leaves)\n\tif (aLo !== bLo) return aLo < bLo\n\treturn aHi < bHi\n}\n\nfunction normalizedEdgeIds(edge: MstEdge, leaves: readonly LeafInput[]): [string, string] {\n\tconst aId = leaves[edge.a].id\n\tconst bId = leaves[edge.b].id\n\treturn aId < bId ? [aId, bId] : [bId, aId]\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|