anywidget-graph 0.3.0__tar.gz → 0.3.1__tar.gz
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.
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/CHANGELOG.md +11 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/PKG-INFO +1 -1
- anywidget_graph-0.3.1/assets/demo.png +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/pyproject.toml +1 -1
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/icons.js +1 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/index.js +150 -57
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/styles.css +1 -4
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/uv.lock +1 -1
- anywidget_graph-0.3.0/assets/demo.png +0 -0
- anywidget_graph-0.3.0/assets/widget.png +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/.github/workflows/ci.yml +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/.github/workflows/pypi.yml +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/.gitignore +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/.python-version +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/LICENSE +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/README.md +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/examples/demo.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/examples/test_hierarchy.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/examples/test_large_graph.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/__init__.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/backends/__init__.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/backends/arango.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/backends/cosmosdb.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/backends/grafeo.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/backends/ladybug.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/backends/neo4j.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/converters/__init__.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/converters/base.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/converters/common.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/converters/cypher.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/converters/gql.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/converters/graphql.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/converters/gremlin.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/converters/sparql.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/demo.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/py.typed +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/__init__.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/grafeo-embed.js +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/grafeo.js +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/neo4j.js +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/properties.js +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/results.js +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/schema.js +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/settings.js +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/ui/toolbar.js +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/src/anywidget_graph/widget.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/tests/__init__.py +0 -0
- {anywidget_graph-0.3.0 → anywidget_graph-0.3.1}/tests/test_graph.py +0 -0
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.1 2026-03-16
|
|
4
|
+
|
|
5
|
+
### New Features
|
|
6
|
+
|
|
7
|
+
- **Lasso selection**: Freeform lasso tool for selecting nodes by drawing around them
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- Fixed missing ICONS import causing zoom and lasso buttons to fail
|
|
12
|
+
- Fixed `lassoSvg` referenced before declaration, crashing widget on load
|
|
13
|
+
|
|
3
14
|
## 0.3.0
|
|
4
15
|
|
|
5
16
|
### New Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anywidget-graph
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Interactive graph visualization for Python notebooks using anywidget
|
|
5
5
|
Project-URL: Homepage, https://grafeo.dev/
|
|
6
6
|
Project-URL: Repository, https://github.com/GrafeoDB/anywidget-graph
|
|
Binary file
|
|
@@ -18,4 +18,5 @@ export const ICONS = {
|
|
|
18
18
|
search: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`,
|
|
19
19
|
chevronUp: `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 15l-6-6-6 6"/></svg>`,
|
|
20
20
|
table: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M3 15h18M9 3v18"/></svg>`,
|
|
21
|
+
lasso: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="10" rx="9" ry="7"/><path d="M12 17v3M12 20l-2 2"/></svg>`,
|
|
21
22
|
};
|
|
@@ -6,6 +6,7 @@ import Graph from "https://esm.sh/graphology@0.25.4";
|
|
|
6
6
|
import Sigma from "https://esm.sh/sigma@3.0.0";
|
|
7
7
|
import * as d3Force from "https://esm.sh/d3-force@3.0.0";
|
|
8
8
|
|
|
9
|
+
import { ICONS } from "./icons.js";
|
|
9
10
|
import { createToolbar } from "./toolbar.js";
|
|
10
11
|
import { createSchemaPanel } from "./schema.js";
|
|
11
12
|
import { createSettingsPanel } from "./settings.js";
|
|
@@ -85,6 +86,18 @@ function getCategoricalColor(value) {
|
|
|
85
86
|
return CATEGORICAL_COLORS[Math.abs(hash) % CATEGORICAL_COLORS.length];
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
function pointInPolygon(x, y, polygon) {
|
|
90
|
+
let inside = false;
|
|
91
|
+
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
92
|
+
const [xi, yi] = polygon[i];
|
|
93
|
+
const [xj, yj] = polygon[j];
|
|
94
|
+
if (((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)) {
|
|
95
|
+
inside = !inside;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return inside;
|
|
99
|
+
}
|
|
100
|
+
|
|
88
101
|
function computeNodeColor(node, colorField, colorScale, colorDomain) {
|
|
89
102
|
if (node.color) return node.color;
|
|
90
103
|
if (!colorField || node[colorField] === undefined) {
|
|
@@ -283,11 +296,16 @@ function render({ model, el }) {
|
|
|
283
296
|
wrapper.style.height = model.get("height") + "px";
|
|
284
297
|
wrapper.style.maxHeight = model.get("height") + "px";
|
|
285
298
|
|
|
286
|
-
//
|
|
299
|
+
// Renderer reference (set after Sigma init, used by theme/filter callbacks)
|
|
300
|
+
let rendererRef = null;
|
|
301
|
+
|
|
302
|
+
// Auto-detect host theme (Tailwind dark/dark-theme class, data-theme, or prefers-color-scheme)
|
|
287
303
|
function detectHostDark() {
|
|
288
304
|
const html = document.documentElement;
|
|
289
|
-
if (html.classList.contains("dark")) return true;
|
|
305
|
+
if (html.classList.contains("dark") || html.classList.contains("dark-theme")) return true;
|
|
290
306
|
if (html.dataset.theme === "dark") return true;
|
|
307
|
+
if (document.body?.classList.contains("dark") || document.body?.classList.contains("dark-theme")) return true;
|
|
308
|
+
if (document.body?.dataset.theme === "dark") return true;
|
|
291
309
|
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) return true;
|
|
292
310
|
return false;
|
|
293
311
|
}
|
|
@@ -299,14 +317,28 @@ function render({ model, el }) {
|
|
|
299
317
|
rendererRef?.refresh();
|
|
300
318
|
}
|
|
301
319
|
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
320
|
+
// Always auto-detect and observe theme changes
|
|
321
|
+
let autoTheme = true;
|
|
322
|
+
model.set("dark_mode", detectHostDark());
|
|
323
|
+
model.save_changes();
|
|
324
|
+
updateTheme();
|
|
325
|
+
|
|
326
|
+
const themeObserver = new MutationObserver(() => {
|
|
327
|
+
if (!autoTheme) return;
|
|
328
|
+
const dark = detectHostDark();
|
|
329
|
+
if (model.get("dark_mode") !== dark) {
|
|
330
|
+
model.set("dark_mode", dark);
|
|
331
|
+
model.save_changes();
|
|
332
|
+
updateTheme();
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ["class", "data-theme", "style"] });
|
|
336
|
+
if (document.body) {
|
|
337
|
+
themeObserver.observe(document.body, { attributes: true, attributeFilter: ["class", "data-theme", "style"] });
|
|
338
|
+
}
|
|
339
|
+
if (window.matchMedia) {
|
|
340
|
+
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
|
|
341
|
+
if (!autoTheme) return;
|
|
310
342
|
const dark = detectHostDark();
|
|
311
343
|
if (model.get("dark_mode") !== dark) {
|
|
312
344
|
model.set("dark_mode", dark);
|
|
@@ -314,13 +346,11 @@ function render({ model, el }) {
|
|
|
314
346
|
updateTheme();
|
|
315
347
|
}
|
|
316
348
|
});
|
|
317
|
-
themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ["class", "data-theme"] });
|
|
318
|
-
} else {
|
|
319
|
-
updateTheme();
|
|
320
349
|
}
|
|
321
350
|
|
|
322
|
-
// Manual toggle from settings panel
|
|
351
|
+
// Manual toggle from settings panel disables auto-theme
|
|
323
352
|
model.on("change:dark_mode", () => {
|
|
353
|
+
autoTheme = false;
|
|
324
354
|
updateTheme();
|
|
325
355
|
});
|
|
326
356
|
|
|
@@ -331,7 +361,6 @@ function render({ model, el }) {
|
|
|
331
361
|
let hiddenNodeTypes = new Set();
|
|
332
362
|
let hiddenEdgeTypes = new Set();
|
|
333
363
|
let typeColorOverrides = new Map(); // custom colors from filter panel
|
|
334
|
-
let rendererRef = null; // set after Sigma init
|
|
335
364
|
|
|
336
365
|
function onFilterChange(nodeTypes, edgeTypes) {
|
|
337
366
|
hiddenNodeTypes = nodeTypes;
|
|
@@ -855,7 +884,8 @@ function render({ model, el }) {
|
|
|
855
884
|
let justDragged = false;
|
|
856
885
|
|
|
857
886
|
renderer.on("downNode", ({ node }) => {
|
|
858
|
-
|
|
887
|
+
const selMode = model.get("selection_mode");
|
|
888
|
+
if (selMode === "box" || selMode === "lasso") return;
|
|
859
889
|
draggedNode = node;
|
|
860
890
|
isDragging = false;
|
|
861
891
|
renderer.getCamera().disable();
|
|
@@ -945,10 +975,16 @@ function render({ model, el }) {
|
|
|
945
975
|
boxModeBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="1" stroke-dasharray="4 2"/></svg>`;
|
|
946
976
|
boxModeBtn.title = "Box select";
|
|
947
977
|
|
|
978
|
+
const lassoModeBtn = document.createElement("button");
|
|
979
|
+
lassoModeBtn.className = "awg-zoom-btn awg-mode-btn";
|
|
980
|
+
lassoModeBtn.innerHTML = ICONS.lasso;
|
|
981
|
+
lassoModeBtn.title = "Lasso select";
|
|
982
|
+
|
|
948
983
|
function updateModeButtons() {
|
|
949
984
|
const mode = model.get("selection_mode");
|
|
950
|
-
clickModeBtn.classList.toggle("awg-mode-active", mode
|
|
985
|
+
clickModeBtn.classList.toggle("awg-mode-active", mode === "click");
|
|
951
986
|
boxModeBtn.classList.toggle("awg-mode-active", mode === "box");
|
|
987
|
+
lassoModeBtn.classList.toggle("awg-mode-active", mode === "lasso");
|
|
952
988
|
}
|
|
953
989
|
updateModeButtons();
|
|
954
990
|
|
|
@@ -960,10 +996,15 @@ function render({ model, el }) {
|
|
|
960
996
|
model.set("selection_mode", "box");
|
|
961
997
|
model.save_changes();
|
|
962
998
|
});
|
|
999
|
+
lassoModeBtn.addEventListener("click", () => {
|
|
1000
|
+
model.set("selection_mode", "lasso");
|
|
1001
|
+
model.save_changes();
|
|
1002
|
+
});
|
|
963
1003
|
model.on("change:selection_mode", updateModeButtons);
|
|
964
1004
|
|
|
965
1005
|
modeGroup.appendChild(clickModeBtn);
|
|
966
1006
|
modeGroup.appendChild(boxModeBtn);
|
|
1007
|
+
modeGroup.appendChild(lassoModeBtn);
|
|
967
1008
|
|
|
968
1009
|
zoomControls.appendChild(zoomFitBtn);
|
|
969
1010
|
zoomControls.appendChild(zoomInBtn);
|
|
@@ -982,64 +1023,116 @@ function render({ model, el }) {
|
|
|
982
1023
|
selectionRect.className = "awg-selection-rect";
|
|
983
1024
|
container.appendChild(selectionRect);
|
|
984
1025
|
|
|
1026
|
+
// Lasso SVG overlay for drawing the freeform path
|
|
1027
|
+
const lassoSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1028
|
+
lassoSvg.style.cssText = "position:absolute;top:0;left:0;width:100%;height:100%;z-index:16;display:none;pointer-events:none;";
|
|
1029
|
+
container.appendChild(lassoSvg);
|
|
1030
|
+
|
|
1031
|
+
const lassoPathEl = document.createElementNS("http://www.w3.org/2000/svg", "polyline");
|
|
1032
|
+
lassoPathEl.setAttribute("fill", "rgba(99, 102, 241, 0.1)");
|
|
1033
|
+
lassoPathEl.setAttribute("stroke", "#6366f1");
|
|
1034
|
+
lassoPathEl.setAttribute("stroke-width", "2");
|
|
1035
|
+
lassoPathEl.setAttribute("stroke-dasharray", "4 2");
|
|
1036
|
+
lassoSvg.appendChild(lassoPathEl);
|
|
1037
|
+
|
|
985
1038
|
function updateSelectionMode() {
|
|
986
1039
|
const mode = model.get("selection_mode");
|
|
987
|
-
|
|
988
|
-
selectionOverlay.style.
|
|
1040
|
+
const active = mode === "box" || mode === "lasso";
|
|
1041
|
+
selectionOverlay.style.pointerEvents = active ? "auto" : "none";
|
|
1042
|
+
selectionOverlay.style.cursor = active ? "crosshair" : "default";
|
|
1043
|
+
lassoSvg.style.display = "none";
|
|
989
1044
|
}
|
|
990
1045
|
updateSelectionMode();
|
|
991
1046
|
model.on("change:selection_mode", updateSelectionMode);
|
|
992
1047
|
|
|
993
1048
|
let boxStart = null;
|
|
1049
|
+
let isLassoing = false;
|
|
1050
|
+
let lassoCoords = [];
|
|
994
1051
|
|
|
995
1052
|
selectionOverlay.addEventListener("mousedown", (e) => {
|
|
996
|
-
|
|
1053
|
+
const mode = model.get("selection_mode");
|
|
997
1054
|
const rect = container.getBoundingClientRect();
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1055
|
+
|
|
1056
|
+
if (mode === "box") {
|
|
1057
|
+
boxStart = { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
1058
|
+
selectionRect.style.display = "block";
|
|
1059
|
+
selectionRect.style.left = boxStart.x + "px";
|
|
1060
|
+
selectionRect.style.top = boxStart.y + "px";
|
|
1061
|
+
selectionRect.style.width = "0px";
|
|
1062
|
+
selectionRect.style.height = "0px";
|
|
1063
|
+
} else if (mode === "lasso") {
|
|
1064
|
+
e.preventDefault();
|
|
1065
|
+
isLassoing = true;
|
|
1066
|
+
lassoCoords = [[e.clientX - rect.left, e.clientY - rect.top]];
|
|
1067
|
+
lassoSvg.style.display = "block";
|
|
1068
|
+
lassoPathEl.setAttribute("points", "");
|
|
1069
|
+
}
|
|
1004
1070
|
});
|
|
1005
1071
|
|
|
1006
1072
|
selectionOverlay.addEventListener("mousemove", (e) => {
|
|
1007
|
-
if (!boxStart) return;
|
|
1008
1073
|
const rect = container.getBoundingClientRect();
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1074
|
+
|
|
1075
|
+
if (boxStart) {
|
|
1076
|
+
const cx = e.clientX - rect.left;
|
|
1077
|
+
const cy = e.clientY - rect.top;
|
|
1078
|
+
const x = Math.min(boxStart.x, cx);
|
|
1079
|
+
const y = Math.min(boxStart.y, cy);
|
|
1080
|
+
const w = Math.abs(cx - boxStart.x);
|
|
1081
|
+
const h = Math.abs(cy - boxStart.y);
|
|
1082
|
+
selectionRect.style.left = x + "px";
|
|
1083
|
+
selectionRect.style.top = y + "px";
|
|
1084
|
+
selectionRect.style.width = w + "px";
|
|
1085
|
+
selectionRect.style.height = h + "px";
|
|
1086
|
+
} else if (isLassoing) {
|
|
1087
|
+
lassoCoords.push([e.clientX - rect.left, e.clientY - rect.top]);
|
|
1088
|
+
lassoPathEl.setAttribute("points", lassoCoords.map(([x, y]) => `${x},${y}`).join(" "));
|
|
1089
|
+
}
|
|
1019
1090
|
});
|
|
1020
1091
|
|
|
1021
1092
|
selectionOverlay.addEventListener("mouseup", (e) => {
|
|
1022
|
-
if (!boxStart) return;
|
|
1023
1093
|
const rect = container.getBoundingClientRect();
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1094
|
+
|
|
1095
|
+
if (boxStart) {
|
|
1096
|
+
const cx = e.clientX - rect.left;
|
|
1097
|
+
const cy = e.clientY - rect.top;
|
|
1098
|
+
const x1 = Math.min(boxStart.x, cx);
|
|
1099
|
+
const y1 = Math.min(boxStart.y, cy);
|
|
1100
|
+
const x2 = Math.max(boxStart.x, cx);
|
|
1101
|
+
const y2 = Math.max(boxStart.y, cy);
|
|
1102
|
+
boxStart = null;
|
|
1103
|
+
selectionRect.style.display = "none";
|
|
1104
|
+
|
|
1105
|
+
// Find nodes inside the box
|
|
1106
|
+
const selected = [];
|
|
1107
|
+
graph.forEachNode((id, attrs) => {
|
|
1108
|
+
const pos = renderer.graphToViewport({ x: attrs.x, y: attrs.y });
|
|
1109
|
+
if (pos.x >= x1 && pos.x <= x2 && pos.y >= y1 && pos.y <= y2) {
|
|
1110
|
+
selected.push(id);
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
model.set("selected_nodes", selected);
|
|
1114
|
+
model.save_changes();
|
|
1115
|
+
} else if (isLassoing) {
|
|
1116
|
+
isLassoing = false;
|
|
1117
|
+
lassoSvg.style.display = "none";
|
|
1118
|
+
|
|
1119
|
+
if (lassoCoords.length < 3) { lassoCoords = []; return; }
|
|
1120
|
+
|
|
1121
|
+
// Close the polygon
|
|
1122
|
+
lassoCoords.push(lassoCoords[0]);
|
|
1123
|
+
|
|
1124
|
+
// Find nodes inside the lasso
|
|
1125
|
+
const selected = [];
|
|
1126
|
+
graph.forEachNode((id, attrs) => {
|
|
1127
|
+
const pos = renderer.graphToViewport({ x: attrs.x, y: attrs.y });
|
|
1128
|
+
if (pointInPolygon(pos.x, pos.y, lassoCoords)) {
|
|
1129
|
+
selected.push(id);
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
1132
|
+
model.set("selected_nodes", selected);
|
|
1133
|
+
model.save_changes();
|
|
1134
|
+
lassoCoords = [];
|
|
1135
|
+
}
|
|
1043
1136
|
});
|
|
1044
1137
|
|
|
1045
1138
|
// Tooltip element
|
|
@@ -515,10 +515,7 @@
|
|
|
515
515
|
color: var(--awg-text);
|
|
516
516
|
}
|
|
517
517
|
|
|
518
|
-
/*
|
|
519
|
-
.awg-wrapper.awg-auto-theme .awg-theme-toggle {
|
|
520
|
-
display: none;
|
|
521
|
-
}
|
|
518
|
+
/* Dark mode toggle always visible — manual toggle disables auto-theme */
|
|
522
519
|
|
|
523
520
|
/* === SCHEMA PANEL (LEFT) === */
|
|
524
521
|
.awg-schema-panel {
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|