Add Bloom map layout with VLAN petals around OME.
Each subnet fans out as a colored petal with spine pulse, soft outlines, and leader-pipe labels. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
clusterHubs: [],
|
||||
laneGuides: [],
|
||||
labelAnchors: [],
|
||||
bloomPetals: [],
|
||||
subnet: "all",
|
||||
model: null,
|
||||
groupHint: null,
|
||||
@@ -250,6 +251,7 @@
|
||||
state.clusterHubs = [];
|
||||
state.laneGuides = [];
|
||||
state.labelAnchors = [];
|
||||
state.bloomPetals = [];
|
||||
setTargets(nodes);
|
||||
}
|
||||
|
||||
@@ -280,6 +282,7 @@
|
||||
state.clusterHubs = [];
|
||||
state.laneGuides = [];
|
||||
state.labelAnchors = [];
|
||||
state.bloomPetals = [];
|
||||
setTargets(nodes);
|
||||
}
|
||||
|
||||
@@ -332,6 +335,79 @@
|
||||
state.clusterHubs = hubs;
|
||||
state.laneGuides = [];
|
||||
state.labelAnchors = anchors;
|
||||
state.bloomPetals = [];
|
||||
setTargets(nodes);
|
||||
}
|
||||
|
||||
function layoutBloom(devices, cx, cy, w, h) {
|
||||
const groups = groupBySubnet(devices);
|
||||
const nodes = [];
|
||||
const anchors = [];
|
||||
const petals = [];
|
||||
const R = Math.min(w, h) * 0.38;
|
||||
const omeHub = { x: cx, y: cy };
|
||||
const nGroups = Math.max(groups.length, 1);
|
||||
|
||||
groups.forEach((g, si) => {
|
||||
const a = (si / nGroups) * Math.PI * 2 - Math.PI / 2;
|
||||
const info = subnetLabelInfo(g.cidr);
|
||||
const tipX = cx + Math.cos(a) * R;
|
||||
const tipY = cy + Math.sin(a) * R;
|
||||
const midX = cx + Math.cos(a) * R * 0.52;
|
||||
const midY = cy + Math.sin(a) * R * 0.52;
|
||||
const petalWidth = 22 + Math.min(48, g.members.length * 2.8);
|
||||
|
||||
petals.push({
|
||||
angle: a,
|
||||
tipX,
|
||||
tipY,
|
||||
midX,
|
||||
midY,
|
||||
width: petalWidth,
|
||||
color: info.color,
|
||||
cidr: g.cidr,
|
||||
label: info.label,
|
||||
count: g.members.length,
|
||||
});
|
||||
|
||||
anchors.push(
|
||||
makeLabelAnchor({
|
||||
x: tipX,
|
||||
y: tipY,
|
||||
cidr: g.cidr,
|
||||
localR: 10,
|
||||
dirX: Math.cos(a),
|
||||
dirY: Math.sin(a),
|
||||
pipeExtra: 28,
|
||||
})
|
||||
);
|
||||
|
||||
g.members.forEach((n, i) => {
|
||||
const t = g.members.length <= 1 ? 0.55 : 0.18 + (i / Math.max(g.members.length - 1, 1)) * 0.72;
|
||||
const alongX = cx + Math.cos(a) * R * t;
|
||||
const alongY = cy + Math.sin(a) * R * t;
|
||||
// Petal bulge: widest mid-petal
|
||||
const bulge = Math.sin(Math.min(1, Math.max(0, (t - 0.12) / 0.76)) * Math.PI);
|
||||
const lane = (i % 2 === 0 ? 1 : -1) * (0.4 + ((i * 7) % 5) * 0.11);
|
||||
const px = -Math.sin(a);
|
||||
const py = Math.cos(a);
|
||||
const spread = bulge * petalWidth * lane;
|
||||
nodes.push({
|
||||
...n,
|
||||
tx: alongX + px * spread,
|
||||
ty: alongY + py * spread,
|
||||
r: nodeRadius(n),
|
||||
depth: 0.65 + t * 0.55,
|
||||
bloom: si,
|
||||
bloomT: t,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
state.clusterHubs = [];
|
||||
state.laneGuides = [];
|
||||
state.labelAnchors = anchors;
|
||||
state.bloomPetals = petals;
|
||||
setTargets(nodes);
|
||||
}
|
||||
|
||||
@@ -438,6 +514,9 @@
|
||||
case "clusters":
|
||||
layoutClusters(devices, cx, cy, w, h);
|
||||
break;
|
||||
case "bloom":
|
||||
layoutBloom(devices, cx, cy, w, h);
|
||||
break;
|
||||
case "lanes":
|
||||
layoutLanes(devices, cx, cy, w, h);
|
||||
break;
|
||||
@@ -626,6 +705,60 @@
|
||||
ctx.globalAlpha = 1;
|
||||
drawPulseLink(hub.x, hub.y, ch.x, ch.y, true, t, (ch.label || "").length * 17, 0.55);
|
||||
}
|
||||
} else if (mode === "bloom") {
|
||||
// Soft petal outlines + spine pulse
|
||||
for (const p of state.bloomPetals || []) {
|
||||
const rgb = hexToRgb(p.color || "#00a8e8");
|
||||
const a = p.angle || 0;
|
||||
const nx = -Math.sin(a);
|
||||
const ny = Math.cos(a);
|
||||
// Petal path: hub → left bulge → tip → right bulge → hub
|
||||
const c1x = hub.x + Math.cos(a) * (Math.hypot(p.midX - hub.x, p.midY - hub.y) || 80);
|
||||
const c1y = hub.y + Math.sin(a) * (Math.hypot(p.midX - hub.x, p.midY - hub.y) || 80);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(hub.x, hub.y);
|
||||
ctx.quadraticCurveTo(
|
||||
c1x + nx * p.width,
|
||||
c1y + ny * p.width,
|
||||
p.tipX,
|
||||
p.tipY
|
||||
);
|
||||
ctx.quadraticCurveTo(
|
||||
c1x - nx * p.width,
|
||||
c1y - ny * p.width,
|
||||
hub.x,
|
||||
hub.y
|
||||
);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = `rgba(${rgb.r},${rgb.g},${rgb.b},0.06)`;
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = `rgba(${rgb.r},${rgb.g},${rgb.b},0.28)`;
|
||||
ctx.lineWidth = 1.2;
|
||||
ctx.stroke();
|
||||
|
||||
// Spine
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(hub.x, hub.y);
|
||||
ctx.lineTo(p.tipX, p.tipY);
|
||||
ctx.strokeStyle = `rgba(${rgb.r},${rgb.g},${rgb.b},0.35)`;
|
||||
ctx.lineWidth = 1.4;
|
||||
ctx.stroke();
|
||||
|
||||
// Traveling bloom spark along spine
|
||||
const u = (t * 0.012 + (p.angle || 0)) % 1;
|
||||
const sx = hub.x + (p.tipX - hub.x) * u;
|
||||
const sy = hub.y + (p.tipY - hub.y) * u;
|
||||
ctx.beginPath();
|
||||
ctx.arc(sx, sy, 2.6, 0, Math.PI * 2);
|
||||
ctx.fillStyle = `rgba(${rgb.r},${rgb.g},${rgb.b},0.85)`;
|
||||
ctx.fill();
|
||||
}
|
||||
// Inner halo
|
||||
ctx.beginPath();
|
||||
ctx.arc(hub.x, hub.y, 42, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = canvasTheme().decor(2);
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
} else if (mode === "lanes") {
|
||||
for (const g of state.laneGuides || []) {
|
||||
ctx.beginPath();
|
||||
@@ -769,6 +902,19 @@
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (mode === "bloom") {
|
||||
// Node → petal mid (spine), plus faint hub link
|
||||
for (const n of state.nodes) {
|
||||
const petal = (state.bloomPetals || [])[n.bloom];
|
||||
if (petal) {
|
||||
drawPulseLink(petal.midX, petal.midY, n.x, n.y, n.connected, t, n.id, 0.75);
|
||||
if (n.connected) drawPulseLink(hub.x, hub.y, petal.midX, petal.midY, true, t, n.id + 9000, 0.35);
|
||||
} else {
|
||||
drawPulseLink(hub.x, hub.y, n.x, n.y, n.connected, t, n.id, 0.8);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (mode === "lanes") {
|
||||
for (const n of state.nodes) {
|
||||
drawPulseLink(hub.x, hub.y, n.x, n.y, n.connected, t, n.id, 0.55);
|
||||
|
||||
+2
-1
@@ -58,6 +58,7 @@
|
||||
<button type="button" class="viz-card active" data-layout="orbit"><span class="viz-ico">◎</span><span class="viz-t">Orbit</span><span class="viz-s">Hub & rings</span></button>
|
||||
<button type="button" class="viz-card" data-layout="galaxy"><span class="viz-ico">✶</span><span class="viz-t">Galaxy</span><span class="viz-s">Spiral arms</span></button>
|
||||
<button type="button" class="viz-card" data-layout="clusters"><span class="viz-ico">◉</span><span class="viz-t">Clusters</span><span class="viz-s">Subnet islands</span></button>
|
||||
<button type="button" class="viz-card" data-layout="bloom"><span class="viz-ico">❀</span><span class="viz-t">Bloom</span><span class="viz-s">VLAN petals</span></button>
|
||||
<button type="button" class="viz-card" data-layout="lanes"><span class="viz-ico">≡</span><span class="viz-t">Lanes</span><span class="viz-s">Fiber highways</span></button>
|
||||
<button type="button" class="viz-card" data-layout="helix"><span class="viz-ico">∿</span><span class="viz-t">Helix</span><span class="viz-s">3D depth coil</span></button>
|
||||
</div>
|
||||
@@ -633,7 +634,7 @@
|
||||
|
||||
<script src="/vendor/xterm/xterm.min.js?v=home1"></script>
|
||||
<script src="/vendor/xterm/xterm-addon-fit.min.js?v=home1"></script>
|
||||
<script src="/app.js?v=labelpipes3"></script>
|
||||
<script src="/app.js?v=bloom1"></script>
|
||||
<script src="/ops.js?v=opsfde2"></script>
|
||||
<script src="/ssh.js?v=fabric4"></script>
|
||||
<script src="/rdp.js?v=fabric4"></script>
|
||||
|
||||
Reference in New Issue
Block a user