doc2gl / visual.html
Doc2GL Deploy
Deploy Doc2GL to HuggingFace Space
eaa2438
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Neo4j Graph Comparison Visualization</title>
<style>
html, body {
font: 16pt Arial;
margin: 0;
padding: 0;
overflow: hidden;
}
#viz {
width: 100vw;
height: 100vh;
border: 1px solid;
background: #f7f9fc;
}
</style>
<script src="neovis.js/dist/neovis.js"></script>
<script
src="http://code.jquery.com/jquery-3.2.1.min.js"
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
crossorigin="anonymous">
</script>
</head>
<body onload="draw()">
<div id="viz"></div>
<script>
// Normalize labels
function normalizeLabel(str) {
return str.replace(/\\/g, '').replace(/\r?\n/g,' ').replace(/\s+/g,' ').trim()
}
// Fonction pour chercher le fichier JSON qui contient les différences entre le GT et les prédictions
async function fetchDiff(doc) {
const url = doc ? `graph_diff_${doc}.json` : 'graph_diff.json'
try {
const r = await fetch(url)
if (!r.ok) throw new Error(r.statusText)
return await r.json()
} catch {
return { missing_nodes:[], extra_nodes:[], missing_edges:[], extra_edges:[] }
}
}
// Fonction l'affichage des noeuds et relations avec coloration
function draw() {
const params = new URLSearchParams(window.location.search)
const doc = params.get('doc')
// ✅ CORRECTION : Filtrer AUSSI le nœud cible (m) pour éviter les doublons
const cypher = doc
? `MATCH p=(n:Entity {doc:'${doc}'})-[r:RELATED_TO {doc:'${doc}'}]->(m:Entity {doc:'${doc}'}) RETURN p`
: `MATCH p=()-[r:RELATED_TO]->() RETURN p`
const viz = new NeoVis.default({
containerId: 'viz',
neo4j: { serverUrl:'bolt://localhost:7687', serverUser:'neo4j', serverPassword:'123456789' },
labels: { Entity:{ label:'name' } },
relationships:{ RELATED_TO:{ label:true } },
visConfig: {
nodes:{
shape: 'box',
size: 25,
font: {
face: 'arial',
size: 20,
color: 'black',
align: 'center'
},
margin: 10,
widthConstraint: {
minimum: 80,
maximum: 200
},
heightConstraint: {
minimum: 40
}
},
edges:{
arrows: { to: { enabled: true, scaleFactor: 0.8 } },
smooth: { type: 'cubicBezier', roundness: 0.4 },
length: 250,
font: {
size: 12,
align: 'horizontal'
}
},
layout: {
hierarchical: {
enabled: true,
direction: 'UD',
sortMethod: 'directed',
levelSeparation: 200,
nodeSpacing: 150,
treeSpacing: 200,
blockShifting: true,
edgeMinimization: true,
parentCentralization: true
}
},
physics: {
enabled: false,
hierarchicalRepulsion: {
nodeDistance: 150
}
},
interaction: {
dragNodes: true,
dragView: true,
zoomView: true
}
},
initialCypher: cypher
})
viz.registerOnEvent('completed', async () => {
const diff = await fetchDiff(doc)
const net = viz.network || viz._network
if (!net) return
// ✅ CORRECTION CLEF : Construire les ensembles correctement
// FP_nodes = extra_nodes = HALLUCINATIONS (en rouge)
// FN_nodes = missing_nodes = NON DÉTECTÉS (en orange)
const FP_nodes = new Set(diff.extra_nodes.map(normalizeLabel)) // 🔴 Hallucinations
const FN_nodes = new Set(diff.missing_nodes.map(normalizeLabel)) // 🟠 Non détectés
const FP_edges = new Set(diff.extra_edges.map(([s,t])=>normalizeLabel(s)+'||'+normalizeLabel(t)))
const FN_edges = new Set(diff.missing_edges.map(([s,t])=>normalizeLabel(s)+'||'+normalizeLabel(t)))
// 🔍 DEBUG: Afficher les données du JSON
console.log('📊 Données du fichier graph_diff.json:')
console.log('🔴 FP_nodes (hallucinations - ROUGE):', Array.from(FP_nodes))
console.log('🟠 FN_nodes (non détectés - ORANGE):', Array.from(FN_nodes))
console.log('FP_edges:', Array.from(FP_edges))
console.log('FN_edges:', Array.from(FN_edges))
// Récupérer les nœuds et arêtes rendus
const nodesArr = net.body.data.nodes.get()
const edgesArr = net.body.data.edges.get()
const testNodeSet = new Set(nodesArr.map(n=>normalizeLabel(n.label)))
const testEdgeSet = new Set(edgesArr.map(e => {
const f = normalizeLabel(nodesArr.find(n=>n.id===e.from).label)
const t = normalizeLabel(nodesArr.find(n=>n.id===e.to).label)
return f+'||'+t
}))
// 🔍 DEBUG: Afficher les nœuds rendus
console.log('📈 Nœuds affichés dans le graphe:', Array.from(testNodeSet))
// ✅ CORRECTION : TP = tous les nœuds affichés qui ne sont NI hallucinations NI non détectés
// TP_nodes = testNodeSet - FP_nodes - FN_nodes
const TP_nodes = new Set([...testNodeSet].filter(lbl => !FP_nodes.has(lbl) && !FN_nodes.has(lbl)))
const TP_edges = new Set([...testEdgeSet].filter(key => !FP_edges.has(key) && !FN_edges.has(key)))
// 🔍 DEBUG: Afficher la classification finale
console.log('✅ TP_nodes (corrects - VERT):', Array.from(TP_nodes))
console.log('🔴 FP_nodes (hallucinations - ROUGE):', Array.from(FP_nodes))
console.log('🟠 FN_nodes (non détectés - ORANGE):', Array.from(FN_nodes))
// ✅ COLORIER LES NŒUDS SELON LA LOGIQUE CORRECTE
// Ordre de priorité : FP (rouge) > FN (orange) > TP (vert)
nodesArr.forEach(n => {
const lbl = normalizeLabel(n.label)
let bg, bd
if (FP_nodes.has(lbl)) {
// 🔴 ROUGE = Hallucinations (extra_nodes)
bg = 'red'
bd = '#c62828'
} else if (FN_nodes.has(lbl)) {
// 🟠 ORANGE = Non détectés (missing_nodes)
bg = 'orange'
bd = '#f9a825'
} else if (TP_nodes.has(lbl)) {
// 🟢 VERT = Corrects
bg = 'green'
bd = '#1B5E20'
} else {
// ⚫ GRIS = Cas par défaut
bg = 'gray'
bd = '#777'
}
net.body.data.nodes.update({
id: n.id,
color: { background: bg, border: bd },
font: { color: 'black', size: 20 }
})
})
// Ajouter les nœuds FN (non détectés) - 🟠 ORANGE
FN_nodes.forEach(lbl => {
if (!testNodeSet.has(lbl)) {
net.body.data.nodes.add({
id: 'FN_node_'+lbl,
label: lbl,
color: { background: 'orange', border: '#f9a825' }, // 🟠 ORANGE
font: { color: 'black', size: 20 },
shape: 'box',
physics: false,
x: Math.random() * 500,
y: Math.random() * 500
})
}
})
// ✅ COLORIER LES ARÊTES
edgesArr.forEach(e => {
const f = normalizeLabel(nodesArr.find(n=>n.id===e.from).label)
const t = normalizeLabel(nodesArr.find(n=>n.id===e.to).label)
const key = f+'||'+t
let col
if (FP_edges.has(key)) {
// 🔴 ROUGE = Hallucinations
col = 'red'
} else if (FN_edges.has(key)) {
// 🟠 ORANGE = Non détectées
col = 'orange'
} else if (TP_edges.has(key)) {
// 🟢 VERT = Correctes
col = 'green'
} else {
// ⚫ GRIS = Cas par défaut
col = 'gray'
}
net.body.data.edges.update({
id: e.id,
color: { color: col },
arrows: { to: { color: col } },
width: 2
})
})
// Ajouter les arêtes FN (non détectées) - 🟠 ORANGE
let idx=0
FN_edges.forEach(key => {
if (!testEdgeSet.has(key)) {
const [fs,ts] = key.split('||')
const fromN = nodesArr.find(n=>normalizeLabel(n.label)===fs)
const toN = nodesArr.find(n=>normalizeLabel(n.label)===ts)
const fromId= fromN?fromN.id:'FN_node_'+fs
const toId = toN?toN.id:'FN_node_'+ts
net.body.data.edges.add({
id: 'FN_edge_'+(idx++),
from: fromId, to: toId,
color: { color: 'orange' }, // 🟠 ORANGE
dashes: true,
arrows: { to: { color: 'orange' } },
width: 2
})
}
})
})
viz.render()
}
</script>
</body>
</html>