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>
117 lines
5.1 KiB
HTML
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}}
|