This commit is contained in:
leejisun9
2025-09-12 11:10:43 +09:00
parent 0f0317e356
commit 2ec34ff321
12 changed files with 5805 additions and 0 deletions

245
pages/test/pathway4.vue Normal file
View File

@@ -0,0 +1,245 @@
<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>