简化图表
该图的大部分内容都是重复的:两个事件都有 8 个 IP,一个事件有 6 个域,一台主机有 9 个文件,10 个目击事件。 UI.simplify.rules 将扮演相同角色的节点折叠成一个在其位置绘制的组。数据未受影响:graph.getNodes() 仍然返回每个节点。
这里有两条规则按顺序运行。 通过传感器是应用程序自己的:它将每个瞄准器的传感器作为密钥,共享密钥的节点成为一组。然后,内置的邻居规则会折叠链接到完全相同节点的一种类型的节点。
- 打开轨道上的 Simplify 模式以关闭规则或更改其最小组。
- 双击某个组可将其成员放回到画布上。拖动它们上面的芯片将它们移动到一起;它的×将它们折叠起来。
- 选择一个组:侧边栏列出其成员,每个组都有拉出。
Simplify页面涵盖了规则、自定义规则和组的外观。
js
const plural = { ip: 'IPs', domain: 'domains', file: 'files', sighting: 'sightings' }
const simplify = {
rules: [
// An app rule: a key per node, and nodes sharing a key become one group.
// It runs first, so the sightings split by sensor before the neighbour
// rule would fold all ten of them together.
{
kind: 'custom',
id: 'bySensor',
label: 'By sensor',
description: 'Sightings reported by the same sensor.',
partition: (view) => new Map(view.nodes
.filter(node => view.typeOf(node) === 'sighting')
.map(node => [node.id, node.getData().sensor])),
},
// The built-in rule: one type, exactly the same neighbours, five or more.
{ kind: 'neighbours', minSize: 5 },
],
typeLabel: (type, count) => `${count} ${plural[type] ?? type}`,
}js
const options = {
UI: {
mode: 'full',
// Off: this card is about neither the minimap nor the data dock.
minimap: false,
table: false,
simplify,
},
// Longer links, so the labels under the groups have room.
simulation: { physics: 'manual', d3LinkDistance: 70 },
render: {
nodeTypeAccessor: (node) => node.getData().type,
nodeStyleMap: {
event: { color: '#7c3aed', size: 16 },
host: { color: '#475569', size: 14 },
ip: { color: '#0ea5e9' },
domain: { color: '#10b981' },
file: { color: '#e11d48' },
sighting: { color: '#f59e0b' },
},
// A group made by the app's rule names its sensor.
groupStyle: (group) => group.rule === 'bySensor'
? { text: () => `${group.members.length} from ${group.members[0].getData().sensor}` }
: undefined,
},
}js
// Two events share eight IPs, the first alone has six domains, a host has nine
// files, and the second event has ten sightings from two sensors.
const ips = Array.from({ length: 8 }, (_, i) => ({ id: `ip-${i}`, data: { type: 'ip', label: `198.51.100.${10 + i}` } }))
const domains = Array.from({ length: 6 }, (_, i) => ({ id: `dom-${i}`, data: { type: 'domain', label: `site-${i}.example` } }))
const files = Array.from({ length: 9 }, (_, i) => ({ id: `file-${i}`, data: { type: 'file', label: `sample-${i}.bin` } }))
const sightings = Array.from({ length: 10 }, (_, i) => ({
id: `seen-${i}`,
data: { type: 'sighting', label: `Sighting ${i + 1}`, sensor: i < 6 ? 'north' : 'south' },
}))
const data = {
nodes: [
{ id: 'ev-1', data: { type: 'event', label: 'Event 1' } },
{ id: 'ev-2', data: { type: 'event', label: 'Event 2' } },
{ id: 'host', data: { type: 'host', label: 'Build host' } },
...ips, ...domains, ...files, ...sightings,
],
edges: [
...ips.flatMap(ip => [{ from: 'ev-1', to: ip.id }, { from: 'ev-2', to: ip.id }]),
...domains.map(domain => ({ from: 'ev-1', to: domain.id })),
...files.map(file => ({ from: 'host', to: file.id })),
...sightings.map(sighting => ({ from: 'ev-2', to: sighting.id })),
{ from: 'ev-2', to: 'host' },
],
}