@krainovsd/graph 0.14.0-beta.3 → 0.14.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/lib/cjs/index.cjs +258 -66
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/esm/module/GraphCanvas/GraphCanvas.js +31 -2
  4. package/lib/esm/module/GraphCanvas/GraphCanvas.js.map +1 -1
  5. package/lib/esm/module/GraphCanvas/lib/utils/link-by-pointer-getter.js +1 -5
  6. package/lib/esm/module/GraphCanvas/lib/utils/link-by-pointer-getter.js.map +1 -1
  7. package/lib/esm/module/GraphCanvas/lib/utils/node-by-pointer-getter.js +1 -5
  8. package/lib/esm/module/GraphCanvas/lib/utils/node-by-pointer-getter.js.map +1 -1
  9. package/lib/esm/module/GraphCanvas/lib/utils/pointer-getter.js +16 -8
  10. package/lib/esm/module/GraphCanvas/lib/utils/pointer-getter.js.map +1 -1
  11. package/lib/esm/module/GraphCanvas/slices/init-area.js +0 -1
  12. package/lib/esm/module/GraphCanvas/slices/init-area.js.map +1 -1
  13. package/lib/esm/module/GraphCanvas/slices/init-dnd.js +4 -14
  14. package/lib/esm/module/GraphCanvas/slices/init-dnd.js.map +1 -1
  15. package/lib/esm/module/GraphCanvas/slices/init-draw.js +13 -0
  16. package/lib/esm/module/GraphCanvas/slices/init-draw.js.map +1 -1
  17. package/lib/esm/module/GraphCanvas/slices/init-pointer.js +39 -38
  18. package/lib/esm/module/GraphCanvas/slices/init-pointer.js.map +1 -1
  19. package/lib/esm/module/GraphCanvas/slices/init-resize.js +1 -1
  20. package/lib/esm/module/GraphCanvas/slices/init-resize.js.map +1 -1
  21. package/lib/esm/module/GraphCanvas/slices/init-selection.js +152 -0
  22. package/lib/esm/module/GraphCanvas/slices/init-selection.js.map +1 -0
  23. package/lib/esm/module/GraphCanvas/slices/init-zoom.js +3 -0
  24. package/lib/esm/module/GraphCanvas/slices/init-zoom.js.map +1 -1
  25. package/lib/index.d.ts +21 -10
  26. package/package.json +1 -1
package/lib/cjs/index.cjs CHANGED
@@ -315,14 +315,22 @@ function computeGraphBounds(nodes) {
315
315
  }
316
316
 
317
317
  function pointerGetter(mouseEvent, areaRect, areaTransform) {
318
- const clientX = "clientX" in mouseEvent
319
- ? mouseEvent.clientX
320
- : (mouseEvent.touches[0]?.clientX ?? mouseEvent.changedTouches[0]?.clientX);
321
- const clientY = "clientX" in mouseEvent
322
- ? mouseEvent.clientY
323
- : (mouseEvent.touches[0]?.clientY ?? mouseEvent.changedTouches[0]?.clientY);
324
- const px = (clientX - areaRect.left - areaTransform.x) / areaTransform.k;
325
- const py = (clientY - areaRect.top - areaTransform.y) / areaTransform.k;
318
+ let localX;
319
+ let localY;
320
+ if ("offsetX" in mouseEvent) {
321
+ localX = mouseEvent.offsetX;
322
+ localY = mouseEvent.offsetY;
323
+ }
324
+ else {
325
+ if (!areaRect)
326
+ return [0, 0];
327
+ const clientX = mouseEvent.touches[0]?.clientX ?? mouseEvent.changedTouches[0]?.clientX;
328
+ const clientY = mouseEvent.touches[0]?.clientY ?? mouseEvent.changedTouches[0]?.clientY;
329
+ localX = clientX - areaRect.left;
330
+ localY = clientY - areaRect.top;
331
+ }
332
+ const px = (localX - areaTransform.x) / areaTransform.k;
333
+ const py = (localY - areaTransform.y) / areaTransform.k;
326
334
  return [px, py];
327
335
  }
328
336
 
@@ -422,10 +430,7 @@ function linkIterationExtractor(link, i, links, state, option, optionConstantGet
422
430
  return customOptions;
423
431
  }
424
432
 
425
- function nodeByPointerGetter({ areaRect, areaTransform, mouseEvent, nodes, }) {
426
- if (!areaRect)
427
- return undefined;
428
- const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
433
+ function nodeByPointerGetter({ pointerX, pointerY, nodes, }) {
429
434
  return d3Array.greatest(nodes, (node) => {
430
435
  if (isOverlapsNode(node, pointerX, pointerY, undefined))
431
436
  return node.index;
@@ -825,10 +830,7 @@ function approximateQuadraticBezierLength(x0, y0, x1, y1, x2, y2) {
825
830
  return (a + b + c) / 2;
826
831
  }
827
832
 
828
- function linkByPointerGetter({ areaRect, areaTransform, mouseEvent, links, linkHoverExtraZone, curve, }) {
829
- if (!areaRect)
830
- return undefined;
831
- const [pointerX, pointerY] = pointerGetter(mouseEvent, areaRect, areaTransform);
833
+ function linkByPointerGetter({ pointerX, pointerY, links, linkHoverExtraZone, curve, }) {
832
834
  return d3Array.greatest(links, (link) => {
833
835
  if (!jsHelpers.isObject(link.source) ||
834
836
  !jsHelpers.isObject(link.target) ||
@@ -1316,7 +1318,6 @@ function initArea() {
1316
1318
  if (!this.area)
1317
1319
  throw new Error("couldn't create canvas");
1318
1320
  this.container.appendChild(this.area);
1319
- this.areaRect = this.area.getBoundingClientRect();
1320
1321
  this.context = this.area.getContext("2d");
1321
1322
  if (!this.context)
1322
1323
  throw new Error("couldn't create canvas context");
@@ -1332,10 +1333,8 @@ function initDnd() {
1332
1333
  if (this.listeners.onDragSubject) {
1333
1334
  return this.listeners.onDragSubject.call(this, event);
1334
1335
  }
1335
- if (!this.areaRect)
1336
- return;
1337
1336
  const mouseEvent = event.sourceEvent;
1338
- const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
1337
+ const [pointerX, pointerY] = this.getPointerAreaPosition(mouseEvent);
1339
1338
  return d3Array.greatest(this.nodes, (node) => {
1340
1339
  if (!node.x || !node.y || (jsHelpers.isBoolean(node.drag) && !node.drag))
1341
1340
  return undefined;
@@ -1351,10 +1350,8 @@ function initDnd() {
1351
1350
  if (this.simulation)
1352
1351
  this.simulation.alphaTarget(0.3).restart();
1353
1352
  }
1354
- if (!this.areaRect)
1355
- return;
1356
1353
  const mouseEvent = event.sourceEvent;
1357
- const [pointerX, pointerY] = pointerGetter(mouseEvent, this.areaRect, this.areaTransform);
1354
+ const [pointerX, pointerY] = this.getPointerAreaPosition(mouseEvent);
1358
1355
  if (this._translateExtent) {
1359
1356
  event.subject.fx = Math.max(this._translateExtent[0][0], Math.min(this._translateExtent[1][0], pointerX));
1360
1357
  event.subject.fy = Math.max(this._translateExtent[0][1], Math.min(this._translateExtent[1][1], pointerY));
@@ -1370,8 +1367,8 @@ function initDnd() {
1370
1367
  if (!event.active && this.simulation)
1371
1368
  this.simulation.alphaTarget(0);
1372
1369
  const sourceEvent = event.sourceEvent;
1373
- if (sourceEvent.altKey && this.areaRect) {
1374
- const [pointerX, pointerY] = pointerGetter(sourceEvent, this.areaRect, this.areaTransform);
1370
+ if (sourceEvent.altKey) {
1371
+ const [pointerX, pointerY] = this.getPointerAreaPosition(sourceEvent);
1375
1372
  if (this._translateExtent) {
1376
1373
  event.subject.fx = Math.max(this._translateExtent[0][0], Math.min(this._translateExtent[1][0], pointerX));
1377
1374
  event.subject.fy = Math.max(this._translateExtent[0][1], Math.min(this._translateExtent[1][1], pointerY));
@@ -2098,6 +2095,19 @@ function initDraw() {
2098
2095
  nodeRenders.forEach((render) => render());
2099
2096
  textRenders.forEach((render) => render());
2100
2097
  this.context.restore();
2098
+ /** selection rectangle */
2099
+ if (this.isSelecting && this.selectionRect) {
2100
+ const rect = this.selectionRect;
2101
+ const screenX = Math.min(rect.x1, rect.x2) * this.areaTransform.k + this.areaTransform.x;
2102
+ const screenY = Math.min(rect.y1, rect.y2) * this.areaTransform.k + this.areaTransform.y;
2103
+ const screenW = Math.abs(rect.x2 - rect.x1) * this.areaTransform.k;
2104
+ const screenH = Math.abs(rect.y2 - rect.y1) * this.areaTransform.k;
2105
+ this.context.fillStyle = "rgba(66, 133, 244, 0.2)";
2106
+ this.context.fillRect(screenX, screenY, screenW, screenH);
2107
+ this.context.strokeStyle = "rgba(66, 133, 244, 0.8)";
2108
+ this.context.lineWidth = 2;
2109
+ this.context.strokeRect(screenX, screenY, screenW, screenH);
2110
+ }
2101
2111
  this.listeners.onDrawFinished?.call?.(this);
2102
2112
  }
2103
2113
  if (this.graphSettings.showDrawTime) {
@@ -2110,7 +2120,7 @@ function initPointer() {
2110
2120
  if (!this.area || !this.nodes)
2111
2121
  throw new Error("bad init data");
2112
2122
  function onHover(event) {
2113
- if (!this.area)
2123
+ if (!this.area || this.isSelecting)
2114
2124
  return;
2115
2125
  let currentNode;
2116
2126
  let currentLink;
@@ -2119,10 +2129,10 @@ function initPointer() {
2119
2129
  let highlightNode = true;
2120
2130
  let highlightLink = true;
2121
2131
  if (checkHighlightNode) {
2132
+ const [pointerX, pointerY] = this.getPointerAreaPosition(event);
2122
2133
  currentNode = nodeByPointerGetter({
2123
- areaRect: this.areaRect,
2124
- areaTransform: this.areaTransform,
2125
- mouseEvent: event,
2134
+ pointerX,
2135
+ pointerY,
2126
2136
  nodes: this.nodes,
2127
2137
  });
2128
2138
  if (currentNode?.highlight != undefined)
@@ -2132,11 +2142,11 @@ function initPointer() {
2132
2142
  this.area.style.cursor = "pointer";
2133
2143
  }
2134
2144
  else if (checkHighlightLink) {
2145
+ const [pointerX, pointerY] = this.getPointerAreaPosition(event);
2135
2146
  currentLink = linkByPointerGetter({
2147
+ pointerX,
2148
+ pointerY,
2136
2149
  linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
2137
- areaRect: this.areaRect,
2138
- areaTransform: this.areaTransform,
2139
- mouseEvent: event,
2140
2150
  links: this.links,
2141
2151
  curve: this.linkSettings.curve,
2142
2152
  });
@@ -2155,19 +2165,20 @@ function initPointer() {
2155
2165
  this.animateHighlight(highlightNode ? currentNode : undefined, highlightLink ? currentLink : undefined);
2156
2166
  if (!this.listeners.onMove)
2157
2167
  return;
2158
- if (!currentNode && !checkHighlightNode)
2168
+ if (!currentNode && !checkHighlightNode) {
2169
+ const [pointerX, pointerY] = this.getPointerAreaPosition(event);
2159
2170
  currentNode = nodeByPointerGetter({
2160
- areaRect: this.areaRect,
2161
- areaTransform: this.areaTransform,
2162
- mouseEvent: event,
2171
+ pointerX,
2172
+ pointerY,
2163
2173
  nodes: this.nodes,
2164
2174
  });
2175
+ }
2165
2176
  if (!currentNode && (!checkHighlightNode || (!checkHighlightLink && !currentLink))) {
2177
+ const [pointerX, pointerY] = this.getPointerAreaPosition(event);
2166
2178
  currentLink = linkByPointerGetter({
2179
+ pointerX,
2180
+ pointerY,
2167
2181
  linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
2168
- areaRect: this.areaRect,
2169
- areaTransform: this.areaTransform,
2170
- mouseEvent: event,
2171
2182
  links: this.links,
2172
2183
  curve: this.linkSettings.curve,
2173
2184
  });
@@ -2181,18 +2192,18 @@ function initPointer() {
2181
2192
  !("button" in event) ||
2182
2193
  event.button !== 1)
2183
2194
  return;
2195
+ const [pointerX, pointerY] = this.getPointerAreaPosition(event);
2184
2196
  const currentNode = nodeByPointerGetter({
2185
- areaRect: this.areaRect,
2186
- areaTransform: this.areaTransform,
2187
- mouseEvent: event,
2197
+ pointerX,
2198
+ pointerY,
2188
2199
  nodes: this.nodes,
2189
2200
  });
2190
2201
  if (!currentNode) {
2202
+ const [pointerX, pointerY] = this.getPointerAreaPosition(event);
2191
2203
  const currentLink = linkByPointerGetter({
2204
+ pointerX,
2205
+ pointerY,
2192
2206
  linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
2193
- areaRect: this.areaRect,
2194
- areaTransform: this.areaTransform,
2195
- mouseEvent: event,
2196
2207
  links: this.links,
2197
2208
  curve: this.linkSettings.curve,
2198
2209
  });
@@ -2203,18 +2214,18 @@ function initPointer() {
2203
2214
  function onRightClick(event) {
2204
2215
  if (!this.listeners.onContextMenu)
2205
2216
  return;
2217
+ const [pointerX, pointerY] = this.getPointerAreaPosition(event);
2206
2218
  const currentNode = nodeByPointerGetter({
2207
- areaRect: this.areaRect,
2208
- areaTransform: this.areaTransform,
2209
- mouseEvent: event,
2219
+ pointerX,
2220
+ pointerY,
2210
2221
  nodes: this.nodes,
2211
2222
  });
2212
2223
  if (!currentNode) {
2224
+ const [pointerX, pointerY] = this.getPointerAreaPosition(event);
2213
2225
  const currentLink = linkByPointerGetter({
2226
+ pointerX,
2227
+ pointerY,
2214
2228
  linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
2215
- areaRect: this.areaRect,
2216
- areaTransform: this.areaTransform,
2217
- mouseEvent: event,
2218
2229
  links: this.links,
2219
2230
  curve: this.linkSettings.curve,
2220
2231
  });
@@ -2225,18 +2236,18 @@ function initPointer() {
2225
2236
  function onDoubleClick(event) {
2226
2237
  if (!this.listeners.onDoubleClick)
2227
2238
  return;
2239
+ const [pointerX, pointerY] = this.getPointerAreaPosition(event);
2228
2240
  const currentNode = nodeByPointerGetter({
2229
- areaRect: this.areaRect,
2230
- areaTransform: this.areaTransform,
2231
- mouseEvent: event,
2241
+ pointerX,
2242
+ pointerY,
2232
2243
  nodes: this.nodes,
2233
2244
  });
2234
2245
  if (!currentNode) {
2246
+ const [pointerX, pointerY] = this.getPointerAreaPosition(event);
2235
2247
  const currentLink = linkByPointerGetter({
2248
+ pointerX,
2249
+ pointerY,
2236
2250
  linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
2237
- areaRect: this.areaRect,
2238
- areaTransform: this.areaTransform,
2239
- mouseEvent: event,
2240
2251
  links: this.links,
2241
2252
  curve: this.linkSettings.curve,
2242
2253
  });
@@ -2247,18 +2258,18 @@ function initPointer() {
2247
2258
  function onClick(event) {
2248
2259
  if (this.isDragging || !this.listeners.onClick || ("button" in event && event.button !== 0))
2249
2260
  return;
2261
+ const [pointerX, pointerY] = this.getPointerAreaPosition(event);
2250
2262
  const currentNode = nodeByPointerGetter({
2251
- areaRect: this.areaRect,
2252
- areaTransform: this.areaTransform,
2253
- mouseEvent: event,
2263
+ pointerX,
2264
+ pointerY,
2254
2265
  nodes: this.nodes,
2255
2266
  });
2256
2267
  if (!currentNode) {
2268
+ const [pointerX, pointerY] = this.getPointerAreaPosition(event);
2257
2269
  const currentLink = linkByPointerGetter({
2270
+ pointerX,
2271
+ pointerY,
2258
2272
  linkHoverExtraZone: this.highlightSettings.linkHoverExtraZone,
2259
- areaRect: this.areaRect,
2260
- areaTransform: this.areaTransform,
2261
- mouseEvent: event,
2262
2273
  links: this.links,
2263
2274
  curve: this.linkSettings.curve,
2264
2275
  });
@@ -2317,11 +2328,161 @@ function initResize() {
2317
2328
  });
2318
2329
  document.addEventListener("visibilitychange", () => {
2319
2330
  if (!document.hidden)
2320
- this.updateRect();
2331
+ this.updateSize();
2321
2332
  }, { signal: abortController.signal });
2322
2333
  observer.observe(this.area);
2323
2334
  }
2324
2335
 
2336
+ function initSelection() {
2337
+ if (!this.area)
2338
+ throw new Error("bad init data");
2339
+ this.isSelecting = false;
2340
+ this.selectionRect = null;
2341
+ let selectedNodesSet = new Set();
2342
+ let selectedLinksSet = new Set();
2343
+ let localSelection = false;
2344
+ function onMouseDown(event) {
2345
+ if (event.button !== 0 || !event.shiftKey)
2346
+ return;
2347
+ if (!this.area)
2348
+ return;
2349
+ event.stopPropagation();
2350
+ event.preventDefault();
2351
+ const [startX, startY] = this.getPointerAreaPosition(event);
2352
+ this.isSelecting = true;
2353
+ localSelection = true;
2354
+ this.selectionRect = { x1: startX, y1: startY, x2: startX, y2: startY };
2355
+ selectedNodesSet = new Set();
2356
+ selectedLinksSet = new Set();
2357
+ const controller = new AbortController();
2358
+ this.area.addEventListener("mousemove", onMouseMove.bind(this), {
2359
+ signal: controller.signal,
2360
+ });
2361
+ this.area.addEventListener("mouseup", () => {
2362
+ onMouseUp.call(this, controller);
2363
+ }, {
2364
+ signal: controller.signal,
2365
+ });
2366
+ window.addEventListener("keyup", (event) => {
2367
+ if (event.key === "Shift") {
2368
+ onMouseUp.call(this, controller);
2369
+ }
2370
+ }, { signal: controller.signal });
2371
+ }
2372
+ function onMouseMove(event) {
2373
+ if (!this.isSelecting || !this.selectionRect)
2374
+ return;
2375
+ event.stopPropagation();
2376
+ const [x, y] = this.getPointerAreaPosition(event);
2377
+ this.selectionRect.x2 = x;
2378
+ this.selectionRect.y2 = y;
2379
+ const rect = normalizeRect(this.selectionRect);
2380
+ const newNodes = new Set();
2381
+ for (let i = 0; i < this.nodes.length; i++) {
2382
+ const node = this.nodes[i];
2383
+ if (node.x == undefined || node.y == undefined)
2384
+ continue;
2385
+ if (node.visible === false)
2386
+ continue;
2387
+ const cache = this.nodeOptionsCache[i];
2388
+ if (isNodeInRect(node, rect, cache)) {
2389
+ newNodes.add(node);
2390
+ }
2391
+ }
2392
+ const newLinks = new Set();
2393
+ for (const link of this.links) {
2394
+ const source = link.source;
2395
+ const target = link.target;
2396
+ if (newNodes.has(source) && newNodes.has(target)) {
2397
+ newLinks.add(link);
2398
+ }
2399
+ }
2400
+ for (const node of selectedNodesSet) {
2401
+ if (!newNodes.has(node)) {
2402
+ node._selected = false;
2403
+ this.listeners.onSelectionOut?.call(this, node, undefined);
2404
+ }
2405
+ }
2406
+ for (const link of selectedLinksSet) {
2407
+ if (!newLinks.has(link)) {
2408
+ link._selected = false;
2409
+ this.listeners.onSelectionOut?.call(this, undefined, link);
2410
+ }
2411
+ }
2412
+ for (const node of newNodes) {
2413
+ if (!selectedNodesSet.has(node)) {
2414
+ node._selected = true;
2415
+ this.listeners.onSelectionIn?.call(this, node, undefined);
2416
+ }
2417
+ }
2418
+ for (const link of newLinks) {
2419
+ if (!selectedLinksSet.has(link)) {
2420
+ link._selected = true;
2421
+ this.listeners.onSelectionIn?.call(this, undefined, link);
2422
+ }
2423
+ }
2424
+ selectedNodesSet = newNodes;
2425
+ selectedLinksSet = newLinks;
2426
+ this.draw();
2427
+ }
2428
+ function onMouseUp(controller) {
2429
+ controller.abort();
2430
+ if (!this.isSelecting)
2431
+ return;
2432
+ const nodes = Array.from(selectedNodesSet);
2433
+ const links = Array.from(selectedLinksSet);
2434
+ for (const node of nodes) {
2435
+ node._selected = false;
2436
+ }
2437
+ for (const link of links) {
2438
+ link._selected = false;
2439
+ }
2440
+ this.isSelecting = false;
2441
+ this.selectionRect = null;
2442
+ selectedNodesSet = new Set();
2443
+ selectedLinksSet = new Set();
2444
+ this.listeners.onSelectionEnd?.call(this, nodes, links);
2445
+ this.tick();
2446
+ }
2447
+ function onSelectionEnd(event) {
2448
+ if (localSelection) {
2449
+ event.stopImmediatePropagation();
2450
+ localSelection = false;
2451
+ }
2452
+ }
2453
+ this.area.addEventListener("mousedown", onMouseDown.bind(this), {
2454
+ signal: this.eventAbortController.signal,
2455
+ });
2456
+ this.area.addEventListener("click", onSelectionEnd, { signal: this.eventAbortController.signal });
2457
+ }
2458
+ function normalizeRect(rect) {
2459
+ const x = Math.min(rect.x1, rect.x2);
2460
+ const y = Math.min(rect.y1, rect.y2);
2461
+ const width = Math.abs(rect.x2 - rect.x1);
2462
+ const height = Math.abs(rect.y2 - rect.y1);
2463
+ return { x, y, width, height };
2464
+ }
2465
+ function isNodeInRect(node, rect, cache) {
2466
+ const nx = node.x;
2467
+ const ny = node.y;
2468
+ if (nx == undefined || ny == undefined)
2469
+ return false;
2470
+ if (cache?.shape === "circle") {
2471
+ const r = cache.radius ?? 0;
2472
+ const closestX = Math.max(rect.x, Math.min(nx, rect.x + rect.width));
2473
+ const closestY = Math.max(rect.y, Math.min(ny, rect.y + rect.height));
2474
+ const dx = nx - closestX;
2475
+ const dy = ny - closestY;
2476
+ return dx * dx + dy * dy <= r * r;
2477
+ }
2478
+ const hw = (cache?.width ?? 0) / 2;
2479
+ const hh = (cache?.height ?? 0) / 2;
2480
+ return (nx - hw >= rect.x &&
2481
+ nx + hw <= rect.x + rect.width &&
2482
+ ny - hh >= rect.y &&
2483
+ ny + hh <= rect.y + rect.height);
2484
+ }
2485
+
2325
2486
  function updateLinkCache() {
2326
2487
  if (!this.context)
2327
2488
  return;
@@ -2574,6 +2735,9 @@ function initZoom(currentZoom) {
2574
2735
  this._translateExtent = translateExtent;
2575
2736
  const zoomInstance = d3Zoom.zoom()
2576
2737
  .scaleExtent(zoomExtent)
2738
+ .filter((event) => {
2739
+ return !(event instanceof MouseEvent && event.shiftKey);
2740
+ })
2577
2741
  .on("zoom", (event) => {
2578
2742
  if (this._zoomAnimating)
2579
2743
  return;
@@ -2798,7 +2962,6 @@ class GraphCanvas {
2798
2962
  simulation;
2799
2963
  areaTransform = d3Zoom.zoomIdentity;
2800
2964
  _translateExtent;
2801
- areaRect;
2802
2965
  draw;
2803
2966
  eventAbortController;
2804
2967
  cachedNodeText = [];
@@ -2816,6 +2979,12 @@ class GraphCanvas {
2816
2979
  _lastNodeZoomK;
2817
2980
  _lastLinkZoomK;
2818
2981
  _zoomAnimating = false;
2982
+ isSelecting = false;
2983
+ selectionRect = null;
2984
+ areaRect;
2985
+ // protected get areaRect() {
2986
+ // return this.area?.getBoundingClientRect();
2987
+ // }
2819
2988
  get simulationWorking() {
2820
2989
  const simulationAlpha = this.simulation?.alpha?.() ?? 0;
2821
2990
  const simulationAlphaMin = this.simulation?.alphaMin?.() ?? 0;
@@ -2973,7 +3142,7 @@ class GraphCanvas {
2973
3142
  this.height = height;
2974
3143
  this.area.width = this.dpi * this.width;
2975
3144
  this.area.height = this.dpi * this.height;
2976
- this.areaRect = this.area.getBoundingClientRect();
3145
+ this.updateRect();
2977
3146
  this.context = this.area.getContext("2d");
2978
3147
  if (!this.context)
2979
3148
  throw new Error("couldn't create canvas context");
@@ -3077,6 +3246,26 @@ class GraphCanvas {
3077
3246
  this.clearState();
3078
3247
  this.clearCache(true);
3079
3248
  };
3249
+ getPointerAreaPosition = (event) => {
3250
+ let localX;
3251
+ let localY;
3252
+ if ("offsetX" in event) {
3253
+ localX = event.offsetX;
3254
+ localY = event.offsetY;
3255
+ }
3256
+ else {
3257
+ const rect = this.areaRect;
3258
+ if (!rect)
3259
+ return [0, 0];
3260
+ const clientX = event.touches[0]?.clientX ?? event.changedTouches[0]?.clientX;
3261
+ const clientY = event.touches[0]?.clientY ?? event.changedTouches[0]?.clientY;
3262
+ localX = clientX - rect.left;
3263
+ localY = clientY - rect.top;
3264
+ }
3265
+ const px = (localX - this.areaTransform.x) / this.areaTransform.k;
3266
+ const py = (localY - this.areaTransform.y) / this.areaTransform.k;
3267
+ return [px, py];
3268
+ };
3080
3269
  clearHTMLElements = () => {
3081
3270
  this.root.replaceChildren();
3082
3271
  this.area = undefined;
@@ -3099,6 +3288,8 @@ class GraphCanvas {
3099
3288
  this.highlightProgress = 0;
3100
3289
  this.highlightStart = null;
3101
3290
  this.highlightPositive = false;
3291
+ this.isSelecting = false;
3292
+ this.selectionRect = null;
3102
3293
  };
3103
3294
  init = () => {
3104
3295
  initArea.call(this);
@@ -3109,6 +3300,7 @@ class GraphCanvas {
3109
3300
  initDnd.call(this);
3110
3301
  initZoom.call(this);
3111
3302
  initResize.call(this);
3303
+ initSelection.call(this);
3112
3304
  initPointer.call(this);
3113
3305
  updateNodeCache.call(this);
3114
3306
  updateLinkCache.call(this);