From c31f8b0b169f49575566b91dc3a08a9c2a92e43a Mon Sep 17 00:00:00 2001 From: ImBenji Date: Thu, 23 Apr 2026 14:28:24 +0100 Subject: [PATCH] add event_date column to event_knowledge and event_predictions tables; update related logic in admin panel and augorWorker --- admin.html | 73 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/admin.html b/admin.html index ec66a9c..f8dcd73 100644 --- a/admin.html +++ b/admin.html @@ -1277,10 +1277,6 @@ function renderIntelGraph(nodes, links) { const g = svg.append('g'); - svg.call( - d3.zoom().scaleExtent([0.15, 5]).on('zoom', ev => g.attr('transform', ev.transform)) - ); - // arrow marker svg.append('defs').append('marker') .attr('id', 'ig-arrow') @@ -1296,32 +1292,27 @@ function renderIntelGraph(nodes, links) { const maxCount = Math.max(...links.map(l => l.count), 1); + const zoomBehavior = d3.zoom().scaleExtent([0.1, 5]).on('zoom', ev => g.attr('transform', ev.transform)); + svg.call(zoomBehavior); + const simulation = d3.forceSimulation(nodes) - .force('link', d3.forceLink(links).id(d => d.key).distance(110)) - .force('charge', d3.forceManyBody().strength(-320)) + .force('link', d3.forceLink(links).id(d => d.key).distance(150)) + .force('charge', d3.forceManyBody().strength(-500)) .force('center', d3.forceCenter(width / 2, height / 2)) - .force('collide', d3.forceCollide(32)); + .force('collide', d3.forceCollide(40)); const linkLines = g.append('g') .selectAll('line') .data(links) .join('line') - .attr('stroke', '#1e293b') - .attr('stroke-width', d => 1 + (d.count / maxCount) * 3.5) - .attr('stroke-opacity', d => 0.35 + (d.count / maxCount) * 0.55) + .attr('stroke', '#334155') + .attr('stroke-width', d => 1 + (d.count / maxCount) * 3) + .attr('stroke-opacity', d => 0.3 + (d.count / maxCount) * 0.6) .attr('marker-end', 'url(#ig-arrow)'); - - const linkLabels = g.append('g') - .selectAll('text') - .data(links) - .join('text') - .text(d => d.type) - .attr('font-size', 9) - .attr('fill', '#334155') - .attr('text-anchor', 'middle') - .attr('pointer-events', 'none'); + // relationship type on hover only via title + linkLines.append('title').text(d => `${d.type} (×${d.count})`); const nodeGroups = g.append('g') @@ -1346,18 +1337,27 @@ function renderIntelGraph(nodes, links) { }); nodeGroups.append('circle') - .attr('r', d => d.tracked ? 14 : 9) + .attr('r', d => d.tracked ? 16 : 11) .attr('fill', d => d.tracked ? '#1e3a5f' : '#0f172a') .attr('stroke', d => d.tracked ? '#3b82f6' : '#475569') .attr('stroke-width', d => d.tracked ? 2 : 1.5); - nodeGroups.append('text') - .text(d => d.ticker || d.name.slice(0, 7)) + // ticker label inside tracked nodes; short name below untracked nodes + nodeGroups.filter(d => d.tracked).append('text') + .text(d => d.ticker || d.name.slice(0, 5)) .attr('text-anchor', 'middle') .attr('dominant-baseline', 'middle') - .attr('font-size', d => d.tracked ? 9 : 8) - .attr('font-weight', d => d.tracked ? '600' : '400') - .attr('fill', d => d.tracked ? '#93c5fd' : '#64748b') + .attr('font-size', 9) + .attr('font-weight', '600') + .attr('fill', '#93c5fd') + .attr('pointer-events', 'none'); + + nodeGroups.filter(d => !d.tracked).append('text') + .text(d => d.name.length > 12 ? d.name.slice(0, 11) + '…' : d.name) + .attr('text-anchor', 'middle') + .attr('y', 20) + .attr('font-size', 9) + .attr('fill', '#475569') .attr('pointer-events', 'none'); nodeGroups.append('title').text(d => d.name); @@ -1370,12 +1370,25 @@ function renderIntelGraph(nodes, links) { .attr('x2', d => d.target.x) .attr('y2', d => d.target.y); - linkLabels - .attr('x', d => (d.source.x + d.target.x) / 2) - .attr('y', d => (d.source.y + d.target.y) / 2 - 4); - nodeGroups.attr('transform', d => `translate(${d.x},${d.y})`); }); + + // fit everything into view once the simulation has cooled enough + simulation.on('end', () => { + const bounds = g.node().getBBox(); + if (!bounds.width || !bounds.height) return; + + const pad = 40; + const scaleX = width / (bounds.width + pad * 2); + const scaleY = height / (bounds.height + pad * 2); + const scale = Math.min(scaleX, scaleY, 1); + + const tx = width / 2 - scale * (bounds.x + bounds.width / 2); + const ty = height / 2 - scale * (bounds.y + bounds.height / 2); + + svg.transition().duration(600) + .call(zoomBehavior.transform, d3.zoomIdentity.translate(tx, ty).scale(scale)); + }); }