Last active
September 23, 2023 16:50
-
-
Save ryanbateman/1ce6c910b747bbe888246bf9dac4549d to your computer and use it in GitHub Desktop.
An in-browser, MQTT topic visualisation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>MQTT Topic tree visualisation experiment</title> | |
<script src="https://d3js.org/d3.v6.min.js"></script> | |
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script> | |
<style> | |
.node-label { | |
max-width: 15px; /* You can adjust this value as needed */ | |
overflow: hidden; | |
text-overflow: ellipsis; | |
white-space: nowrap; | |
} | |
</style> | |
</head> | |
<body> | |
<svg width="960" height="1500"></svg> | |
<script> | |
const svg = d3.select("svg"); | |
const width = +svg.attr("width"); | |
const height = +svg.attr("height"); | |
const g = svg.append("g").attr("transform", "translate(40,40)"); | |
const treeLayout = d3.tree().size([height - 200, width - 160]).separation((a, b) => (a.parent == b.parent ? 1 : 2) / a.depth); | |
const rootNode = { | |
name: "Root", | |
children: [] | |
}; | |
function drawTree(data) { | |
const root = d3.hierarchy(data); | |
treeLayout(root); | |
svg.selectAll('*').remove(); | |
const g = svg.append("g").attr("transform", "translate(10,10)"); | |
const links = g.selectAll(".link") | |
.data(root.links()) | |
.enter() | |
.append("path") | |
.attr("class", "link") | |
.attr("d", d3.linkHorizontal().x(d => d.y).y(d => d.x)) | |
.style("fill", "none") | |
.style("stroke", d => d.target.data.isFlashing ? "#F6BE00" : "#ccc") | |
.style("stroke-width", d => d.target.data.isFlashing ? "0.3em" : "0.1em"); | |
links.filter(d => d.target.data.isFlashing) | |
.transition() | |
.duration(5000) | |
.style("stroke", "#ccc") | |
.style("stroke-width", "0.1em") | |
.on("end", d => d.target.data.isFlashing = false); | |
const nodes = g.selectAll(".node") | |
.data(root.descendants()) | |
.enter() | |
.append("circle") | |
.attr("class", "node") | |
.attr("cx", d => d.y) | |
.attr("cy", d => d.x) | |
.attr("r", 4) | |
.style("fill", d => d.data.isFlashing ? "#F6BE00" : (d.data.isNew ? "red" : "#666")); | |
nodes.filter(d => d.data.isNew) | |
.transition() | |
.duration(3000) | |
.style("fill", "#666") | |
.on("end", d => d.data.isNew = false); | |
nodes.filter(d => d.data.isFlashing) | |
.transition() | |
.duration(5000) | |
.style("fill", "#666") | |
.on("end", d => d.data.isFlashing = false); | |
g.selectAll(".node-label") | |
.data(root.descendants()) | |
.enter() | |
.append("text") | |
.attr("class", "node-label") | |
.attr("x", d => d.y) | |
.attr("y", d => d.x) | |
.attr("dy", "0.35em") | |
.attr("dx", d => d.children ? "-15px" : "15px") | |
.attr("text-anchor", d => d.children ? "end" : "start") | |
.text(d => d.data.name) | |
.style("font-family", "Arial") | |
.style("font-size", "8px"); | |
} | |
function flashNodesForTopic(topic) { | |
let segments = topic.split('/'); | |
let currentNode = rootNode; | |
segments.forEach(segment => { | |
if (currentNode.children) { | |
let existingNode = currentNode.children.find(n => n.name === segment); | |
if (existingNode) { | |
existingNode.isFlashing = true; | |
currentNode = existingNode; | |
} | |
} | |
}); | |
drawTree(rootNode); | |
} | |
function updateTreeWithNewNode(topic) { | |
let segments = topic.split('/'); | |
let currentNode = rootNode; | |
segments.forEach(segment => { | |
if (!currentNode.children) { | |
currentNode.children = []; | |
} | |
let existingNode = currentNode.children.find(n => n.name === segment); | |
if (!existingNode) { | |
let newNode = { name: segment, isNew: true }; | |
currentNode.children.push(newNode); | |
existingNode = newNode; | |
} | |
currentNode = existingNode; | |
}); | |
drawTree(rootNode); | |
} | |
const client = mqtt.connect("mqtt://192.168.178.17:9001"); | |
client.on("connect", () => { | |
client.subscribe("#", (err) => { | |
if (!err) { | |
console.log("Connected"); | |
} | |
}); | |
}); | |
client.on('message', function (topic, message) { | |
flashNodesForTopic(topic.toString()); | |
updateTreeWithNewNode(topic.toString()); | |
}); | |
drawTree(rootNode); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment