mirror of
https://github.com/MISP/misp-galaxy.git
synced 2025-02-17 01:06:22 +00:00
commit
17066667f9
1 changed files with 196 additions and 14 deletions
|
@ -1,10 +1,16 @@
|
||||||
document$.subscribe(function () {
|
document$.subscribe(function () {
|
||||||
|
|
||||||
const NODE_RADIUS = 8;
|
const NODE_RADIUS = 8;
|
||||||
const NODE_COLOR = "#69b3a2";
|
// const NODE_COLOR = "#69b3a2";
|
||||||
const Parent_Node_COLOR = "#ff0000";
|
const Parent_Node_COLOR = "#ff0000";
|
||||||
|
|
||||||
|
|
||||||
|
function applyTableFilter(tf) {
|
||||||
|
var valuesToSelect = ['1', '2', '3'];
|
||||||
|
tf.setFilterValue(4, valuesToSelect);
|
||||||
|
tf.filter();
|
||||||
|
};
|
||||||
|
|
||||||
function parseFilteredTable(tf, allData) {
|
function parseFilteredTable(tf, allData) {
|
||||||
var data = [];
|
var data = [];
|
||||||
tf.getFilteredData().forEach((row, i) => {
|
tf.getFilteredData().forEach((row, i) => {
|
||||||
|
@ -55,7 +61,8 @@ document$.subscribe(function () {
|
||||||
var newNodes = Array.from(new Set(newData.flatMap(d => [d.source, d.target])))
|
var newNodes = Array.from(new Set(newData.flatMap(d => [d.source, d.target])))
|
||||||
.map(id => ({
|
.map(id => ({
|
||||||
id,
|
id,
|
||||||
path: nodePaths[id]
|
path: nodePaths[id],
|
||||||
|
galaxy: newData.find(d => d.source === id) ? newData.find(d => d.source === id).sourceGalaxy : newData.find(d => d.target === id).targetGalaxy
|
||||||
}));
|
}));
|
||||||
|
|
||||||
var newLinks = newData.map(d => ({ source: d.source, target: d.target }));
|
var newLinks = newData.map(d => ({ source: d.source, target: d.target }));
|
||||||
|
@ -76,10 +83,39 @@ document$.subscribe(function () {
|
||||||
nodePaths[d.target] = d.targetPath || null;
|
nodePaths[d.target] = d.targetPath || null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Extract unique galaxy names from data
|
||||||
|
const galaxies = Array.from(new Set(data.flatMap(d => [d.sourceGalaxy, d.targetGalaxy])));
|
||||||
|
|
||||||
|
const colorScheme = [
|
||||||
|
'#E63946', // Red
|
||||||
|
'#F1FAEE', // Off White
|
||||||
|
'#A8DADC', // Light Blue
|
||||||
|
'#457B9D', // Medium Blue
|
||||||
|
'#1D3557', // Dark Blue
|
||||||
|
'#F4A261', // Sandy Brown
|
||||||
|
'#2A9D8F', // Teal
|
||||||
|
'#E9C46A', // Saffron
|
||||||
|
'#F77F00', // Orange
|
||||||
|
'#D62828', // Dark Red
|
||||||
|
'#023E8A', // Royal Blue
|
||||||
|
'#0077B6', // Light Sea Blue
|
||||||
|
'#0096C7', // Sky Blue
|
||||||
|
'#00B4D8', // Bright Sky Blue
|
||||||
|
'#48CAE4', // Light Blue
|
||||||
|
'#90E0EF', // Powder Blue
|
||||||
|
'#ADE8F4', // Pale Cerulean
|
||||||
|
'#CAF0F8', // Blithe Blue
|
||||||
|
'#FFBA08', // Selective Yellow
|
||||||
|
'#FFD60A' // Naples Yellow
|
||||||
|
];
|
||||||
|
const colorScale = d3.scaleOrdinal(colorScheme)
|
||||||
|
.domain(galaxies);
|
||||||
|
|
||||||
var nodes = Array.from(new Set(data.flatMap(d => [d.source, d.target])))
|
var nodes = Array.from(new Set(data.flatMap(d => [d.source, d.target])))
|
||||||
.map(id => ({
|
.map(id => ({
|
||||||
id,
|
id,
|
||||||
path: nodePaths[id]
|
path: nodePaths[id],
|
||||||
|
galaxy: data.find(d => d.source === id) ? data.find(d => d.source === id).sourceGalaxy : data.find(d => d.target === id).targetGalaxy
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let header = document.querySelector('h1').textContent;
|
let header = document.querySelector('h1').textContent;
|
||||||
|
@ -104,9 +140,9 @@ document$.subscribe(function () {
|
||||||
|
|
||||||
var simulation = d3.forceSimulation(nodes)
|
var simulation = d3.forceSimulation(nodes)
|
||||||
.force("link", d3.forceLink(links).id(d => d.id).distance(linkDistance))
|
.force("link", d3.forceLink(links).id(d => d.id).distance(linkDistance))
|
||||||
.force("charge", d3.forceManyBody().strength(-50))
|
.force("charge", d3.forceManyBody().strength(-50).distanceMax(500))
|
||||||
.force("center", d3.forceCenter(width / 2, height / 2))
|
.force("center", d3.forceCenter(width / 2, height / 2))
|
||||||
.alphaDecay(0.02); // A lower value, adjust as needed
|
.alphaDecay(0.05); // A lower value, adjust as needed
|
||||||
|
|
||||||
// Create links
|
// Create links
|
||||||
var link = svg.append("g")
|
var link = svg.append("g")
|
||||||
|
@ -115,7 +151,7 @@ document$.subscribe(function () {
|
||||||
.selectAll("line")
|
.selectAll("line")
|
||||||
.data(links)
|
.data(links)
|
||||||
.enter().append("line")
|
.enter().append("line")
|
||||||
.attr("stroke-width", d => Math.sqrt(d.value));
|
.attr("stroke-width", 1);
|
||||||
|
|
||||||
// Create nodes
|
// Create nodes
|
||||||
var node = svg.append("g")
|
var node = svg.append("g")
|
||||||
|
@ -128,8 +164,9 @@ document$.subscribe(function () {
|
||||||
return d.id === Parent_Node.id ? NODE_RADIUS + 5 : NODE_RADIUS;
|
return d.id === Parent_Node.id ? NODE_RADIUS + 5 : NODE_RADIUS;
|
||||||
})
|
})
|
||||||
.attr("fill", function (d, i) {
|
.attr("fill", function (d, i) {
|
||||||
return d.id === Parent_Node.id ? Parent_Node_COLOR : NODE_COLOR;
|
return d.id === Parent_Node.id ? Parent_Node_COLOR : colorScale(d.galaxy);
|
||||||
});
|
})
|
||||||
|
.attr("class", d => "node galaxy-" + d.galaxy.replace(/\s+/g, '-').replace(/[\s.]/g, '-'));
|
||||||
|
|
||||||
// Apply tooltip on nodes
|
// Apply tooltip on nodes
|
||||||
node.on("mouseover", function (event, d) {
|
node.on("mouseover", function (event, d) {
|
||||||
|
@ -139,17 +176,42 @@ document$.subscribe(function () {
|
||||||
tooltip.html(d.id)
|
tooltip.html(d.id)
|
||||||
.style("left", (event.pageX) + "px")
|
.style("left", (event.pageX) + "px")
|
||||||
.style("top", (event.pageY - 28) + "px");
|
.style("top", (event.pageY - 28) + "px");
|
||||||
|
node.style("opacity", 0.1);
|
||||||
|
link.style("opacity", 0.1);
|
||||||
|
d3.select(this)
|
||||||
|
.attr("r", parseFloat(d3.select(this).attr("r")) + 5)
|
||||||
|
.style("opacity", 1);
|
||||||
|
svg.selectAll(".legend-text.galaxy-" + d.galaxy.replace(/\s+/g, '-').replace(/[\s.]/g, '-'))
|
||||||
|
.style("font-weight", "bold")
|
||||||
|
.style("font-size", "14px");
|
||||||
|
link.filter(l => l.source.id === d.id || l.target.id === d.id)
|
||||||
|
.attr("stroke-width", 3)
|
||||||
|
.style("opacity", 1);
|
||||||
|
node.filter(n => n.id === d.id || links.some(l => (l.source.id === d.id && l.target.id === n.id) || (l.target.id === d.id && l.source.id === n.id)))
|
||||||
|
.style("opacity", 1);
|
||||||
})
|
})
|
||||||
.on("mousemove", function (event) {
|
.on("mousemove", function (event) {
|
||||||
tooltip.style("left", (event.pageX) + "px")
|
tooltip.style("left", (event.pageX) + "px")
|
||||||
.style("top", (event.pageY - 28) + "px");
|
.style("top", (event.pageY - 28) + "px");
|
||||||
})
|
})
|
||||||
.on("mouseout", function (d) {
|
.on("mouseout", function (event, d) {
|
||||||
tooltip.transition()
|
tooltip.transition()
|
||||||
.duration(500)
|
.duration(500)
|
||||||
.style("opacity", 0);
|
.style("opacity", 0);
|
||||||
|
node.style("opacity", 1);
|
||||||
|
link.style("opacity", 1);
|
||||||
|
d3.select(this).attr("r", function (d, i) {
|
||||||
|
return d.id === Parent_Node.id ? NODE_RADIUS + 5 : NODE_RADIUS;
|
||||||
|
});
|
||||||
|
svg.selectAll(".legend-text.galaxy-" + d.galaxy.replace(/\s+/g, '-').replace(/[\s.]/g, '-'))
|
||||||
|
.style("font-weight", "normal")
|
||||||
|
.style("font-size", "12px");
|
||||||
|
link.filter(l => l.source.id === d.id || l.target.id === d.id)
|
||||||
|
.attr("stroke-width", 1);
|
||||||
|
node.filter(n => n.id === d.id || links.some(l => (l.source.id === d.id && l.target.id === n.id) || (l.target.id === d.id && l.source.id === n.id)))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Apply links on nodes
|
// Apply links on nodes
|
||||||
node.on("dblclick", function (event, d) {
|
node.on("dblclick", function (event, d) {
|
||||||
location.href = d.path;
|
location.href = d.path;
|
||||||
|
@ -180,6 +242,93 @@ document$.subscribe(function () {
|
||||||
if (!event.active) simulation.alphaTarget(0);
|
if (!event.active) simulation.alphaTarget(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepare legend data
|
||||||
|
const legendData = galaxies.map(galaxy => ({
|
||||||
|
name: galaxy,
|
||||||
|
color: colorScale(galaxy)
|
||||||
|
}));
|
||||||
|
|
||||||
|
const maxCharLength = 10; // Maximum number of characters to display in legend
|
||||||
|
// Create legend
|
||||||
|
const legend = svg.append("g")
|
||||||
|
.attr("class", "legend")
|
||||||
|
.attr("transform", "translate(" + (width - 100) + ",20)"); // Adjust position as needed
|
||||||
|
|
||||||
|
// Add legend title
|
||||||
|
legend.append("text")
|
||||||
|
.attr("x", 0)
|
||||||
|
.attr("y", -10)
|
||||||
|
.style("font-size", "13px")
|
||||||
|
.style("text-anchor", "start")
|
||||||
|
.style("fill", "grey")
|
||||||
|
.text("Galaxy Colors");
|
||||||
|
|
||||||
|
// Add colored rectangles and text labels for each galaxy
|
||||||
|
const legendItem = legend.selectAll(".legend-item")
|
||||||
|
.data(legendData)
|
||||||
|
.enter().append("g")
|
||||||
|
.attr("class", "legend-item")
|
||||||
|
.attr("transform", (d, i) => `translate(0, ${i * 20})`);
|
||||||
|
|
||||||
|
legendItem.append("rect")
|
||||||
|
.attr("width", 12)
|
||||||
|
.attr("height", 12)
|
||||||
|
.style("fill", d => d.color)
|
||||||
|
.on("mouseover", function (event, d) {
|
||||||
|
node.style("opacity", 0.1);
|
||||||
|
link.style("opacity", 0.1);
|
||||||
|
svg.selectAll(".galaxy-" + d.name.replace(/\s+/g, '-').replace(/[\s.]/g, '-'))
|
||||||
|
.each(function () {
|
||||||
|
var currentRadius = d3.select(this).attr("r");
|
||||||
|
d3.select(this).style("opacity", 1);
|
||||||
|
});
|
||||||
|
tooltip.transition()
|
||||||
|
.duration(200)
|
||||||
|
.style("opacity", .9);
|
||||||
|
tooltip.html(d.name)
|
||||||
|
.style("left", (event.pageX) + "px")
|
||||||
|
.style("top", (event.pageY - 28) + "px");
|
||||||
|
})
|
||||||
|
.on("mouseout", function (event, d) {
|
||||||
|
node.style("opacity", 1);
|
||||||
|
link.style("opacity", 1);
|
||||||
|
tooltip.transition()
|
||||||
|
.duration(500)
|
||||||
|
.style("opacity", 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
legendItem.append("text")
|
||||||
|
.attr("x", 24)
|
||||||
|
.attr("y", 9)
|
||||||
|
.attr("dy", "0.35em")
|
||||||
|
.style("text-anchor", "start")
|
||||||
|
.style("fill", "grey")
|
||||||
|
.style("font-size", "12px")
|
||||||
|
.attr("class", d => "legend-text galaxy-" + d.name.replace(/\s+/g, '-').replace(/[\s.]/g, '-'))
|
||||||
|
.text(d => d.name.length > maxCharLength ? d.name.substring(0, maxCharLength) + "..." : d.name)
|
||||||
|
.on("mouseover", function (event, d) {
|
||||||
|
node.style("opacity", 0.1);
|
||||||
|
link.style("opacity", 0.1);
|
||||||
|
svg.selectAll(".galaxy-" + d.name.replace(/\s+/g, '-').replace(/[\s.]/g, '-'))
|
||||||
|
.each(function () {
|
||||||
|
d3.select(this).style("opacity", 1);
|
||||||
|
});
|
||||||
|
tooltip.transition()
|
||||||
|
.duration(200)
|
||||||
|
.style("opacity", .9);
|
||||||
|
tooltip.html(d.name)
|
||||||
|
.style("left", (event.pageX) + "px")
|
||||||
|
.style("top", (event.pageY - 28) + "px");
|
||||||
|
})
|
||||||
|
.on("mouseout", function (event, d) {
|
||||||
|
node.style("opacity", 1);
|
||||||
|
link.style("opacity", 1);
|
||||||
|
tooltip.transition()
|
||||||
|
.duration(500)
|
||||||
|
.style("opacity", 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// Update positions on each simulation 'tick'
|
// Update positions on each simulation 'tick'
|
||||||
simulation.on("tick", () => {
|
simulation.on("tick", () => {
|
||||||
nodes.forEach(d => {
|
nodes.forEach(d => {
|
||||||
|
@ -210,8 +359,9 @@ document$.subscribe(function () {
|
||||||
return d.id === Parent_Node.id ? NODE_RADIUS + 5 : NODE_RADIUS;
|
return d.id === Parent_Node.id ? NODE_RADIUS + 5 : NODE_RADIUS;
|
||||||
})
|
})
|
||||||
.attr("fill", function (d, i) {
|
.attr("fill", function (d, i) {
|
||||||
return d.id === Parent_Node.id ? Parent_Node_COLOR : NODE_COLOR;
|
return d.id === Parent_Node.id ? Parent_Node_COLOR : colorScale(d.galaxy);
|
||||||
}),
|
})
|
||||||
|
.attr("class", d => "node galaxy-" + d.galaxy.replace(/\s+/g, '-').replace(/[\s.]/g, '-')),
|
||||||
update => update,
|
update => update,
|
||||||
exit => exit.remove()
|
exit => exit.remove()
|
||||||
);
|
);
|
||||||
|
@ -226,15 +376,39 @@ document$.subscribe(function () {
|
||||||
tooltip.html(d.id)
|
tooltip.html(d.id)
|
||||||
.style("left", (event.pageX) + "px")
|
.style("left", (event.pageX) + "px")
|
||||||
.style("top", (event.pageY - 28) + "px");
|
.style("top", (event.pageY - 28) + "px");
|
||||||
|
node.style("opacity", 0.1);
|
||||||
|
link.style("opacity", 0.1);
|
||||||
|
d3.select(this)
|
||||||
|
.attr("r", parseFloat(d3.select(this).attr("r")) + 5)
|
||||||
|
.style("opacity", 1);
|
||||||
|
svg.selectAll(".legend-text.galaxy-" + d.galaxy.replace(/\s+/g, '-').replace(/[\s.]/g, '-'))
|
||||||
|
.style("font-weight", "bold")
|
||||||
|
.style("font-size", "14px");
|
||||||
|
link.filter(l => l.source.id === d.id || l.target.id === d.id)
|
||||||
|
.attr("stroke-width", 3)
|
||||||
|
.style("opacity", 1);
|
||||||
|
node.filter(n => n.id === d.id || links.some(l => (l.source.id === d.id && l.target.id === n.id) || (l.target.id === d.id && l.source.id === n.id)))
|
||||||
|
.style("opacity", 1);
|
||||||
})
|
})
|
||||||
.on("mousemove", function (event) {
|
.on("mousemove", function (event) {
|
||||||
tooltip.style("left", (event.pageX) + "px")
|
tooltip.style("left", (event.pageX) + "px")
|
||||||
.style("top", (event.pageY - 28) + "px");
|
.style("top", (event.pageY - 28) + "px");
|
||||||
})
|
})
|
||||||
.on("mouseout", function (d) {
|
.on("mouseout", function (event, d) {
|
||||||
tooltip.transition()
|
tooltip.transition()
|
||||||
.duration(500)
|
.duration(500)
|
||||||
.style("opacity", 0);
|
.style("opacity", 0);
|
||||||
|
node.style("opacity", 1);
|
||||||
|
link.style("opacity", 1);
|
||||||
|
d3.select(this).attr("r", function (d, i) {
|
||||||
|
return d.id === Parent_Node.id ? NODE_RADIUS + 5 : NODE_RADIUS;
|
||||||
|
});
|
||||||
|
svg.selectAll(".legend-text.galaxy-" + d.galaxy.replace(/\s+/g, '-').replace(/[\s.]/g, '-'))
|
||||||
|
.style("font-weight", "normal")
|
||||||
|
.style("font-size", "12px");
|
||||||
|
link.filter(l => l.source.id === d.id || l.target.id === d.id)
|
||||||
|
.attr("stroke-width", 1);
|
||||||
|
node.filter(n => n.id === d.id || links.some(l => (l.source.id === d.id && l.target.id === n.id) || (l.target.id === d.id && l.source.id === n.id)))
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply links on nodes
|
// Apply links on nodes
|
||||||
|
@ -261,6 +435,8 @@ document$.subscribe(function () {
|
||||||
// Restart the simulation with new data
|
// Restart the simulation with new data
|
||||||
simulation.nodes(nodes);
|
simulation.nodes(nodes);
|
||||||
simulation.force("link").links(links);
|
simulation.force("link").links(links);
|
||||||
|
linkDistance = Math.sqrt((width * height) / nodes.length);
|
||||||
|
simulation.force("link").distance(linkDistance);
|
||||||
simulation.alpha(1).restart();
|
simulation.alpha(1).restart();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -307,7 +483,13 @@ document$.subscribe(function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
tf.init();
|
tf.init();
|
||||||
var data = parseTable(table);
|
var allData = parseTable(table);
|
||||||
|
if (allData.length > 1000) {
|
||||||
|
applyTableFilter(tf);
|
||||||
|
data = parseFilteredTable(tf, allData);
|
||||||
|
} else {
|
||||||
|
data = allData;
|
||||||
|
}
|
||||||
var graphId = "graph" + index;
|
var graphId = "graph" + index;
|
||||||
var div = document.createElement("div");
|
var div = document.createElement("div");
|
||||||
div.id = graphId;
|
div.id = graphId;
|
||||||
|
@ -316,7 +498,7 @@ document$.subscribe(function () {
|
||||||
|
|
||||||
// Listen for table filtering events
|
// Listen for table filtering events
|
||||||
tf.emitter.on(['after-filtering'], function () {
|
tf.emitter.on(['after-filtering'], function () {
|
||||||
filterTableAndGraph(tf, simulation, data);
|
filterTableAndGraph(tf, simulation, allData);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue