261 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			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>
							 |