talas-group/talas-wiki/templates/graph.html
senke 66471934af Initial commit: Talas Group project management & documentation
Knowledge base of ~80+ markdown files across 14 domains (00-13),
Logseq graph, hardware design files (KiCAD), infrastructure configs,
and talas-wiki static site.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 20:10:41 +02:00

117 lines
5.1 KiB
HTML

{{define "title"}}Graph — Talas Wiki{{end}}
{{define "content"}}
<div class="breadcrumb">
<a href="/">/</a> / <span>graph</span>
</div>
<h1>Graph des connexions</h1>
<p class="subtitle">Visualisation des liens entre documents. Cliquer sur un noeud pour naviguer.</p>
<div class="graph-controls">
<label>Filtre domaine:
<select id="graph-domain-filter">
<option value="">Tous</option>
{{range .Domains}}
<option value="{{.FullDir}}">{{.Number}} {{.Name}}</option>
{{end}}
</select>
</label>
<button onclick="resetGraph()" class="btn-edit">reset zoom</button>
</div>
<div id="graph-container" class="graph-container"></div>
{{end}}
{{define "scripts"}}
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
var domainColors = {};
var container = document.getElementById('graph-container');
var width = container.offsetWidth;
var height = Math.max(600, window.innerHeight - 250);
var svg = d3.select('#graph-container').append('svg')
.attr('width', width).attr('height', height)
.style('background', '#0e0e0e')
.style('border', '1px solid #2e3440');
var g = svg.append('g');
var zoom = d3.zoom().scaleExtent([0.1, 4]).on('zoom', function(e) {
g.attr('transform', e.transform);
});
svg.call(zoom);
function resetGraph() { svg.transition().duration(500).call(zoom.transform, d3.zoomIdentity); }
fetch('/api/graph').then(function(r){return r.json()}).then(function(data) {
// Build domain colors from API response
if (data.colors) {
Object.keys(data.colors).forEach(function(num) {
// Map num (e.g. "01") to domain names found in nodes
data.nodes.forEach(function(n) {
if (n.domain && n.domain.substring(0,2) === num) {
domainColors[n.domain] = data.colors[num];
}
});
});
}
var simulation = d3.forceSimulation(data.nodes)
.force('link', d3.forceLink(data.links).id(function(d){return d.id}).distance(80))
.force('charge', d3.forceManyBody().strength(-120))
.force('center', d3.forceCenter(width/2, height/2))
.force('collision', d3.forceCollide(20));
var link = g.append('g').selectAll('line').data(data.links).enter().append('line')
.attr('stroke', '#2e3440').attr('stroke-width', 0.5).attr('stroke-opacity', 0.6);
var node = g.append('g').selectAll('circle').data(data.nodes).enter().append('circle')
.attr('r', function(d){ return Math.min(3 + Math.sqrt(d.size) * 2, 14); })
.attr('fill', function(d){ return domainColors[d.domain] || '#a3be8c'; })
.attr('stroke', '#121212').attr('stroke-width', 0.5)
.style('cursor', 'pointer')
.call(d3.drag()
.on('start', function(e,d){ if(!e.active) simulation.alphaTarget(0.3).restart(); d.fx=d.x; d.fy=d.y; })
.on('drag', function(e,d){ d.fx=e.x; d.fy=e.y; })
.on('end', function(e,d){ if(!e.active) simulation.alphaTarget(0); d.fx=null; d.fy=null; })
);
var label = g.append('g').selectAll('text').data(data.nodes).enter().append('text')
.text(function(d){ return d.title.length > 25 ? d.title.substring(0,25)+'...' : d.title; })
.attr('font-size', 8).attr('fill', '#4c566a').attr('dx', 10).attr('dy', 3)
.style('pointer-events', 'none').attr('font-family', 'JetBrains Mono, Consolas, monospace');
node.on('click', function(e, d) {
window.location = '/wiki/' + d.id;
});
node.on('mouseover', function(e, d) {
d3.select(this).attr('stroke', '#eceff4').attr('stroke-width', 2);
link.attr('stroke', function(l){ return (l.source.id===d.id||l.target.id===d.id) ? '#88c0d0' : '#2e3440'; })
.attr('stroke-width', function(l){ return (l.source.id===d.id||l.target.id===d.id) ? 1.5 : 0.5; });
label.attr('fill', function(l){ return l.id===d.id ? '#eceff4' : '#4c566a'; })
.attr('font-size', function(l){ return l.id===d.id ? 11 : 8; });
}).on('mouseout', function() {
d3.select(this).attr('stroke', '#121212').attr('stroke-width', 0.5);
link.attr('stroke', '#2e3440').attr('stroke-width', 0.5);
label.attr('fill', '#4c566a').attr('font-size', 8);
});
// Domain filter
document.getElementById('graph-domain-filter').addEventListener('change', function() {
var domain = this.value;
node.style('opacity', function(d){ return !domain || d.domain===domain ? 1 : 0.1; });
label.style('opacity', function(d){ return !domain || d.domain===domain ? 1 : 0.05; });
link.style('opacity', function(l){ return !domain || l.source.domain===domain || l.target.domain===domain ? 0.6 : 0.05; });
});
simulation.on('tick', function() {
link.attr('x1',function(d){return d.source.x}).attr('y1',function(d){return d.source.y})
.attr('x2',function(d){return d.target.x}).attr('y2',function(d){return d.target.y});
node.attr('cx',function(d){return d.x}).attr('cy',function(d){return d.y});
label.attr('x',function(d){return d.x}).attr('y',function(d){return d.y});
});
});
</script>
{{end}}