Update app.py
Browse files
app.py
CHANGED
|
@@ -459,7 +459,13 @@ def make_figure(d: int,
|
|
| 459 |
|
| 460 |
switch_vertices = set()
|
| 461 |
if switch_active and sw_s is not None and sw_e is not None and path:
|
| 462 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 463 |
|
| 464 |
|
| 465 |
# ---------- neighbors of path vertices ----------
|
|
@@ -586,22 +592,29 @@ def make_figure(d: int,
|
|
| 586 |
)
|
| 587 |
|
| 588 |
# ---------- highlight switch-dims selection edges ----------
|
| 589 |
-
if switch_active and sw_s is not None and sw_e is not None and
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 603 |
)
|
| 604 |
-
|
| 605 |
|
| 606 |
|
| 607 |
# ---------- nodes ----------
|
|
@@ -1057,57 +1070,82 @@ def update_path(clickData,
|
|
| 1057 |
# If we're in switch-dims mode, vertex clicks define exactly 3 consecutive vertices
|
| 1058 |
if switchsel.get("active") and isinstance(cd, (int, float)):
|
| 1059 |
vid = int(cd)
|
| 1060 |
-
|
| 1061 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1062 |
return path, subsel, switchsel
|
| 1063 |
-
|
| 1064 |
s = switchsel.get("start_idx")
|
| 1065 |
e = switchsel.get("end_idx")
|
| 1066 |
-
|
| 1067 |
# first vertex
|
| 1068 |
if s is None:
|
| 1069 |
return path, subsel, {"active": True, "start_idx": idx, "end_idx": idx}
|
| 1070 |
-
|
| 1071 |
if e is None:
|
| 1072 |
e = s
|
| 1073 |
-
|
| 1074 |
-
#
|
| 1075 |
-
|
| 1076 |
-
|
| 1077 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1078 |
# if we have 3 vertices selected: apply switch
|
| 1079 |
-
if
|
| 1080 |
-
x =
|
| 1081 |
-
y =
|
| 1082 |
-
z =
|
| 1083 |
-
|
| 1084 |
a = edge_dimension(x, y)
|
| 1085 |
b = edge_dimension(y, z)
|
| 1086 |
if a is None or b is None:
|
| 1087 |
-
# not a valid 2-edge segment, exit mode
|
| 1088 |
return path, subsel, {"active": False, "start_idx": None, "end_idx": None}
|
| 1089 |
-
|
| 1090 |
# new middle vertex to traverse b then a
|
| 1091 |
y2 = x ^ (1 << b)
|
| 1092 |
-
|
| 1093 |
-
#
|
| 1094 |
-
if y2
|
| 1095 |
return path, subsel, {"active": False, "start_idx": None, "end_idx": None}
|
| 1096 |
-
|
| 1097 |
-
|
| 1098 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1099 |
return new_path, subsel, {"active": False, "start_idx": None, "end_idx": None}
|
| 1100 |
-
|
| 1101 |
-
return path, subsel,
|
| 1102 |
-
|
| 1103 |
# allow shrinking by clicking current end
|
| 1104 |
if idx == e:
|
| 1105 |
-
|
|
|
|
|
|
|
|
|
|
| 1106 |
return path, subsel, {"active": True, "start_idx": s, "end_idx": new_e}
|
| 1107 |
-
|
| 1108 |
# otherwise ignore
|
| 1109 |
return path, subsel, switchsel
|
| 1110 |
|
|
|
|
| 1111 |
# If we're in subpath selection mode, vertex clicks define (start_idx, end_idx)
|
| 1112 |
if subsel.get("active") and isinstance(cd, (int, float)):
|
| 1113 |
vid = int(cd)
|
|
|
|
| 459 |
|
| 460 |
switch_vertices = set()
|
| 461 |
if switch_active and sw_s is not None and sw_e is not None and path:
|
| 462 |
+
is_closed = (len(path) >= 2 and path[0] == path[-1])
|
| 463 |
+
cycle = path[:-1] if is_closed else path[:]
|
| 464 |
+
n = len(cycle)
|
| 465 |
+
if n > 0:
|
| 466 |
+
dist = (sw_e - sw_s) % n
|
| 467 |
+
switch_vertices = {cycle[(sw_s + i) % n] for i in range(dist + 1)}
|
| 468 |
+
|
| 469 |
|
| 470 |
|
| 471 |
# ---------- neighbors of path vertices ----------
|
|
|
|
| 592 |
)
|
| 593 |
|
| 594 |
# ---------- highlight switch-dims selection edges ----------
|
| 595 |
+
if switch_active and sw_s is not None and sw_e is not None and path:
|
| 596 |
+
is_closed = (len(path) >= 2 and path[0] == path[-1])
|
| 597 |
+
cycle = path[:-1] if is_closed else path[:]
|
| 598 |
+
n = len(cycle)
|
| 599 |
+
if n > 0:
|
| 600 |
+
dist = (sw_e - sw_s) % n
|
| 601 |
+
for k in range(dist):
|
| 602 |
+
a = cycle[(sw_s + k) % n]
|
| 603 |
+
b = cycle[(sw_s + k + 1) % n]
|
| 604 |
+
x1, y1 = pos[a]
|
| 605 |
+
x2, y2 = pos[b]
|
| 606 |
+
edge_traces.append(
|
| 607 |
+
go.Scatter(
|
| 608 |
+
x=[x1, x2],
|
| 609 |
+
y=[y1, y2],
|
| 610 |
+
mode="lines",
|
| 611 |
+
line=dict(width=max(2, edge_w * 3), color="#DC2626"),
|
| 612 |
+
opacity=1.0,
|
| 613 |
+
hoverinfo="skip",
|
| 614 |
+
name="switch dims selection",
|
| 615 |
+
)
|
| 616 |
)
|
| 617 |
+
|
| 618 |
|
| 619 |
|
| 620 |
# ---------- nodes ----------
|
|
|
|
| 1070 |
# If we're in switch-dims mode, vertex clicks define exactly 3 consecutive vertices
|
| 1071 |
if switchsel.get("active") and isinstance(cd, (int, float)):
|
| 1072 |
vid = int(cd)
|
| 1073 |
+
|
| 1074 |
+
# Work on a cycle if path is explicitly closed
|
| 1075 |
+
is_closed = (len(path) >= 2 and path[0] == path[-1])
|
| 1076 |
+
cycle = path[:-1] if is_closed else path[:]
|
| 1077 |
+
n = len(cycle)
|
| 1078 |
+
|
| 1079 |
+
if n < 3:
|
| 1080 |
+
return path, subsel, {"active": False, "start_idx": None, "end_idx": None}
|
| 1081 |
+
|
| 1082 |
+
# In a proper cycle representation, vertices are unique
|
| 1083 |
+
try:
|
| 1084 |
+
idx = cycle.index(vid)
|
| 1085 |
+
except ValueError:
|
| 1086 |
return path, subsel, switchsel
|
| 1087 |
+
|
| 1088 |
s = switchsel.get("start_idx")
|
| 1089 |
e = switchsel.get("end_idx")
|
| 1090 |
+
|
| 1091 |
# first vertex
|
| 1092 |
if s is None:
|
| 1093 |
return path, subsel, {"active": True, "start_idx": idx, "end_idx": idx}
|
| 1094 |
+
|
| 1095 |
if e is None:
|
| 1096 |
e = s
|
| 1097 |
+
|
| 1098 |
+
# distance forward on the cycle (0,1,2,...)
|
| 1099 |
+
dist = (e - s) % n
|
| 1100 |
+
|
| 1101 |
+
# must extend consecutively forward (with wrap)
|
| 1102 |
+
expected = (e + 1) % n
|
| 1103 |
+
if idx == expected and dist < 2:
|
| 1104 |
+
new_e = idx
|
| 1105 |
+
new_dist = (new_e - s) % n
|
| 1106 |
+
|
| 1107 |
# if we have 3 vertices selected: apply switch
|
| 1108 |
+
if new_dist == 2:
|
| 1109 |
+
x = cycle[s]
|
| 1110 |
+
y = cycle[(s + 1) % n]
|
| 1111 |
+
z = cycle[(s + 2) % n]
|
| 1112 |
+
|
| 1113 |
a = edge_dimension(x, y)
|
| 1114 |
b = edge_dimension(y, z)
|
| 1115 |
if a is None or b is None:
|
|
|
|
| 1116 |
return path, subsel, {"active": False, "start_idx": None, "end_idx": None}
|
| 1117 |
+
|
| 1118 |
# new middle vertex to traverse b then a
|
| 1119 |
y2 = x ^ (1 << b)
|
| 1120 |
+
|
| 1121 |
+
# verify it actually reaches z by traversing a next
|
| 1122 |
+
if edge_dimension(y2, z) != a:
|
| 1123 |
return path, subsel, {"active": False, "start_idx": None, "end_idx": None}
|
| 1124 |
+
|
| 1125 |
+
# avoid creating duplicates in the cycle (allow x,z)
|
| 1126 |
+
if y2 in set(cycle) and y2 not in {x, z}:
|
| 1127 |
+
return path, subsel, {"active": False, "start_idx": None, "end_idx": None}
|
| 1128 |
+
|
| 1129 |
+
cycle2 = cycle[:]
|
| 1130 |
+
cycle2[(s + 1) % n] = y2
|
| 1131 |
+
|
| 1132 |
+
new_path = cycle2 + [cycle2[0]] if is_closed else cycle2
|
| 1133 |
return new_path, subsel, {"active": False, "start_idx": None, "end_idx": None}
|
| 1134 |
+
|
| 1135 |
+
return path, subsel, {"active": True, "start_idx": s, "end_idx": new_e}
|
| 1136 |
+
|
| 1137 |
# allow shrinking by clicking current end
|
| 1138 |
if idx == e:
|
| 1139 |
+
if e != s:
|
| 1140 |
+
new_e = (e - 1) % n
|
| 1141 |
+
else:
|
| 1142 |
+
new_e = s
|
| 1143 |
return path, subsel, {"active": True, "start_idx": s, "end_idx": new_e}
|
| 1144 |
+
|
| 1145 |
# otherwise ignore
|
| 1146 |
return path, subsel, switchsel
|
| 1147 |
|
| 1148 |
+
|
| 1149 |
# If we're in subpath selection mode, vertex clicks define (start_idx, end_idx)
|
| 1150 |
if subsel.get("active") and isinstance(cd, (int, float)):
|
| 1151 |
vid = int(cd)
|