Files
bio_frontend/pages/[tabId]/test/pathway4.vue

261 lines
7.1 KiB
Vue

<template>
<div ref="cyContainer" class="cy-container"></div>
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
import cytoscape from "cytoscape";
let CytoscapeOverlays = null;
if (import.meta.client) {
const useOverlay = (await import("@/composables/useOverlay")).default;
CytoscapeOverlays = await useOverlay();
}
const cyContainer = ref(null);
onMounted(async () => {
await nextTick();
cytoscape.use(CytoscapeOverlays.default);
const res = await fetch("/expanded_pathway21600.xml");
const xmlText = await res.text();
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlText, "application/xml");
const scale = 3;
const entryMap = new Map();
const entryDataMap = new Map();
const parentMap = new Map();
const entries = Array.from(xmlDoc.getElementsByTagName("entry"));
for (const entry of entries) {
const id = entry.getAttribute("id");
entryDataMap.set(id, entry);
if (entry.getAttribute("type") === "group") {
const components = entry.getElementsByTagName("component");
for (const comp of components) {
parentMap.set(comp.getAttribute("id"), id);
}
}
}
const nodes = entries.map(entry => {
const id = entry.getAttribute("id");
const graphics = entry.getElementsByTagName("graphics")[0];
const x = parseFloat(graphics?.getAttribute("x") || "0") * scale;
const y = parseFloat(graphics?.getAttribute("y") || "0") * scale;
const label = graphics?.getAttribute("name") || id;
const fgColor = graphics?.getAttribute("fgcolor") || "#000000";
const bgColor = graphics?.getAttribute("bgcolor") || "#ffffff";
const parent = parentMap.get(id);
const valueA = Math.floor(Math.random() * 50);
const valueB = 100 - valueA;
const node = {
data: {
id,
label,
link: entry.getAttribute("link") || null,
reaction: entry.getAttribute("reaction") || null,
chartData: [valueA, valueB],
},
position: { x, y },
classes: entry.getAttribute("type"),
style: {
color: fgColor,
"background-color": bgColor,
},
};
if (parent) node.data.parent = parent;
entryMap.set(id, true);
return node;
});
function resolveToRealNode(id) {
if (!entryMap.has(id)) return null;
const entry = entryDataMap.get(id);
if (entry?.getAttribute("type") === "group") {
const components = Array.from(entry.getElementsByTagName("component"));
if (components.length > 0) return components[0].getAttribute("id");
}
return id;
}
const edges = [];
const relations = Array.from(xmlDoc.getElementsByTagName("relation"));
relations.forEach((rel, i) => {
const source = resolveToRealNode(rel.getAttribute("entry1"));
const target = resolveToRealNode(rel.getAttribute("entry2"));
const type = rel.getAttribute("type");
const subtypes = Array.from(rel.getElementsByTagName("subtype"));
const compoundSubtype = subtypes.find(
s => s.getAttribute("name") === "compound"
);
if (compoundSubtype) {
const compoundId = compoundSubtype.getAttribute("value");
if (
entryMap.has(source) &&
entryMap.has(target) &&
entryMap.has(compoundId)
) {
const sourceConst = source;
const targetConst = target;
edges.push(
{
data: {
id: `edge${i}-1`,
source: sourceConst,
target: compoundId,
label: "via compound",
},
},
{
data: {
id: `edge${i}-2`,
source: compoundId,
target: targetConst,
label: "via compound",
},
}
);
}
} else {
if (entryMap.has(source) && entryMap.has(target)) {
const sourceConst = source;
const targetConst = target;
edges.push({
data: {
id: `edge${i}`,
source: sourceConst,
target: targetConst,
label: type,
},
});
}
}
});
const reactions = Array.from(xmlDoc.getElementsByTagName("reaction"));
reactions.forEach((reaction, i) => {
const reactionType = reaction.getAttribute("type");
const substrates = Array.from(reaction.getElementsByTagName("substrate"));
const products = Array.from(reaction.getElementsByTagName("product"));
substrates.forEach(substrate => {
const sid = resolveToRealNode(substrate.getAttribute("id"));
products.forEach(product => {
const pid = resolveToRealNode(product.getAttribute("id"));
if (entryMap.has(sid) && entryMap.has(pid)) {
edges.push({
data: {
id: `reaction-${i}-${sid}-${pid}`,
source: sid,
target: pid,
label: `${reactionType}`,
},
});
}
});
});
});
// 원형(도넛/파이) 차트 overlay 생성 (흰색 라인 없이)
const pieOverlay = CytoscapeOverlays.renderSymbol({
symbol: node => {
const data = node.data("chartData") || [50, 50];
return {
draw: (ctx, size) => {
const total = data[0] + data[1];
const r = Math.sqrt(size / Math.PI);
const innerR = r * 0.5;
const startAngle = 0;
const angleA = (data[0] / total) * 2 * Math.PI;
// A 영역
ctx.beginPath();
ctx.arc(0, 0, r, startAngle, startAngle + angleA);
ctx.arc(0, 0, innerR, startAngle + angleA, startAngle, true);
ctx.closePath();
ctx.fillStyle = "#36a2eb";
ctx.fill();
// B 영역
ctx.beginPath();
ctx.arc(0, 0, r, startAngle + angleA, startAngle + 2 * Math.PI);
ctx.arc(
0,
0,
innerR,
startAngle + 2 * Math.PI,
startAngle + angleA,
true
);
ctx.closePath();
ctx.fillStyle = "#ff6384";
ctx.fill();
},
};
},
color: "",
width: 32,
height: 32,
borderColor: "#333",
});
const cy = cytoscape({
container: cyContainer.value,
elements: { nodes, edges },
style: [
{
selector: "node",
style: {
label: "data(label)",
"background-color": "#eee",
"text-valign": "center",
"text-halign": "center",
"border-width": 2,
"border-color": "#333",
"font-size": 8,
padding: "6px",
width: 32,
height: 32,
},
},
{
selector: "edge",
style: {
"curve-style": "bezier",
"target-arrow-shape": "triangle",
"line-color": "#888",
},
},
],
layout: { name: "preset", padding: 100 },
});
cy.overlays([{ position: "right", vis: pieOverlay }], {
updateOn: "render",
backgroundColor: "white",
});
applyCustomZoomHandling(cy);
});
</script>
<style>
.cy-container {
width: 100vw !important;
max-width: 100vw !important;
min-width: 0 !important;
height: 800px !important;
overflow: hidden !important;
position: relative;
}
</style>