add event_date column to event_knowledge and event_predictions tables; update related logic in admin panel and augorWorker
This commit is contained in:
parent
8aa10d9bc2
commit
8d259eb4d1
1 changed files with 29 additions and 14 deletions
43
admin.html
43
admin.html
|
|
@ -1390,15 +1390,30 @@ function renderIntelGraph() {
|
|||
const zoomBehavior = d3.zoom().scaleExtent([0.08, 6]).on('zoom', ev => g.attr('transform', ev.transform));
|
||||
svg.call(zoomBehavior);
|
||||
|
||||
// degree map — nodes with many connections need stronger repulsion
|
||||
const degree = {};
|
||||
for (const l of linksCopy) {
|
||||
degree[l.source] = (degree[l.source] || 0) + 1;
|
||||
degree[l.target] = (degree[l.target] || 0) + 1;
|
||||
}
|
||||
const maxDegree = Math.max(...Object.values(degree), 1);
|
||||
|
||||
const simulation = d3.forceSimulation(nodesCopy)
|
||||
.force('link', d3.forceLink(linksCopy).id(d => d.key).distance(d => {
|
||||
// tracked-tracked links spread wider
|
||||
const bothTracked = typeof d.source === 'object' ? d.source.tracked && d.target.tracked : false;
|
||||
return bothTracked ? 220 : 160;
|
||||
const src = typeof d.source === 'object' ? d.source.key : d.source;
|
||||
const tgt = typeof d.target === 'object' ? d.target.key : d.target;
|
||||
const deg = Math.max(degree[src] || 1, degree[tgt] || 1);
|
||||
// busier nodes push their neighbors further away
|
||||
return 180 + deg * 18;
|
||||
}))
|
||||
.force('charge', d3.forceManyBody().strength(d => {
|
||||
const deg = degree[d.key] || 1;
|
||||
return -1400 - deg * 200;
|
||||
}))
|
||||
.force('charge', d3.forceManyBody().strength(-900))
|
||||
.force('center', d3.forceCenter(width / 2, height / 2))
|
||||
.force('collide', d3.forceCollide(d => d.tracked ? 52 : 38));
|
||||
// collision radius covers circle + name text below it
|
||||
.force('collide', d3.forceCollide(d => d.tracked ? 72 : 52))
|
||||
.alphaDecay(0.015);
|
||||
|
||||
|
||||
// links
|
||||
|
|
@ -1408,22 +1423,22 @@ function renderIntelGraph() {
|
|||
.data(linksCopy)
|
||||
.join('line')
|
||||
.attr('stroke', '#334155')
|
||||
.attr('stroke-width', d => {
|
||||
// more dramatic: 1px at count=1, up to 7px at maxCount
|
||||
return 1 + Math.pow(d.count / maxCount, 0.6) * 6;
|
||||
})
|
||||
.attr('stroke-opacity', d => 0.25 + (d.count / maxCount) * 0.65)
|
||||
.attr('stroke-width', d => 1 + Math.pow(d.count / maxCount, 0.55) * 6)
|
||||
.attr('stroke-opacity', d => 0.2 + (d.count / maxCount) * 0.7)
|
||||
.attr('marker-end', 'url(#ig-arrow)');
|
||||
|
||||
// edge labels — size and opacity proportional to count
|
||||
// edge labels — only on edges where count >= median, to avoid hub clutter
|
||||
const sortedCounts = linksCopy.map(l => l.count).sort((a, b) => a - b);
|
||||
const medianCount = sortedCounts[Math.floor(sortedCounts.length / 2)] || 1;
|
||||
|
||||
const linkLabels = linkG.selectAll('text')
|
||||
.data(linksCopy)
|
||||
.data(linksCopy.filter(l => l.count >= medianCount))
|
||||
.join('text')
|
||||
.text(d => d.type)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('font-size', d => 7 + Math.round((d.count / maxCount) * 4))
|
||||
.attr('font-size', d => 8 + Math.round((d.count / maxCount) * 3))
|
||||
.attr('fill', '#475569')
|
||||
.attr('opacity', d => 0.4 + (d.count / maxCount) * 0.5)
|
||||
.attr('opacity', d => 0.45 + (d.count / maxCount) * 0.45)
|
||||
.attr('pointer-events', 'none');
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue