File size: 9,067 Bytes
eaa2438 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 | <!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> |