Skip to content

Files

Latest commit

553ef7d · Aug 31, 2018

History

History
522 lines (440 loc) · 18.1 KB

uorb_graph.md

File metadata and controls

522 lines (440 loc) · 18.1 KB

uORB Publication/Subscription Graph

This page provides a uORB publication/subscription graph that shows the communication between modules. It is based on information that is extracted directly from the source code. Usage instructions are provided below.

Search: Preset: FMUv4 Modules FMUv2 Modules SITL Modules SITL Runtime Modules All Modules

<script src="https://d3js.org/d3.v4.min.js"></script>
<script> // the d3.js script might not yet be loaded (because it's not in ), so we // wrap everything in a function and retry until d3 is available function initializeGraph() { if (typeof d3 === 'undefined') { // try again later setTimeout(initializeGraph, 500); return; } var graph_option = document.getElementById("select-graph"); var default_json_file = graph_option.value; var minOpacity = 0.1; // opacity when a node/link is faded /* search field: highlight all matching nodes on text change */ var g_filterText = ""; function searchTextChanged() { var textField = document.getElementById("search"); var searchText = textField.value; var opacity = minOpacity; if (searchText == "" || document.activeElement != textField) { opacity = 1; g_filterText = ""; } else { g_filterText = searchText; } /* change opacity */ // TODO: call fade() instead? node.style("stroke-opacity", function(o) { thisOpacity = o.name.includes(searchText) ? 1 : opacity; this.setAttribute('fill-opacity', thisOpacity); return thisOpacity; }); text.style("stroke-opacity", function(o) { thisOpacity = o.name.includes(searchText) ? 1 : opacity; this.setAttribute('fill-opacity', thisOpacity); return thisOpacity; }); link.style("stroke-opacity", function(o) { return opacity; }); } document.getElementById("search").addEventListener("keyup", searchTextChanged); document.getElementById("search").addEventListener("focusout", searchTextChanged); document.getElementById("search").addEventListener("focusin", searchTextChanged); document.getElementById("select-graph").addEventListener("change", reloadSimulation); var svg = d3.select("svg"), width = +svg.attr("width"), height = +svg.attr("height"); var collisionForce = rectCollide() .size(function (d) { return [d.width+10, d.height+6]; }); var boxForce = boundedBox() .bounds([[0, 0], [width, height]]) .size(function (d) { return [d.width, d.height]; }); var simulation = d3.forceSimulation() .velocityDecay(0.3) // default: 0.4 // alpha: initially 1, then reduced at each step, reducing the forces, so // that the simulation comes to a stop eventually .alphaMin(0.0001) // default: 0.001 .alphaDecay(0.0428) // default: 0.0228 //.alphaTarget(1) // enabling this will make sure the simulation never comes // to a stop (and the nodes will either keep fighting for their position, or // find an equilibrium) .force("link", d3.forceLink().id(function(d) { return d.id; }) .distance(100)//.strength(0.02) // default: 30, 1 / Math.min(count(link.source), count(link.target)); // distance: desired link distance // .iterations(1) // default: 1, greater = increased rigidity ) .force("charge", d3.forceManyBody().strength(-250)) // decrease to make the // graph spread more (distance has a similar effect, but affects the // leaf nodes more) .force('box', boxForce) // keep the nodes inside the visible area .force('collision', collisionForce) .force("center", d3.forceCenter(width / 2, height / 2)); // SVG elements var node = null; var text = null; var link = null; function loadSimulation(json_file_name) { d3.json(json_file_name, function(error, graph) { if (error) throw error; // module filtering (does not remove 'orphaned' topics) /* var ignored_modules = ["mavlink", "commander"]; for (var i = 0; i < ignored_modules.length; ++i) { var module_id = "m_"+ignored_modules[i]; // links for (var j = 0; j < graph.links.length; ++j) { if (graph.links[j].source == module_id || graph.links[j].target == module_id) { graph.links.splice(j, 1); --j; } } // nodes for (var j = 0; j < graph.nodes.length; ++j) { if (graph.nodes[j].id == module_id) { graph.nodes.splice(j, 1); --j; } } } */ // explanation for the following syntax: https://bost.ocks.org/mike/join/ link = svg.append("g") .attr("class", "links") .selectAll("line") .data(graph.links) .enter().append("line") .attr("stroke-opacity", 0.7) .attr("stroke", function(d) { return d.color; }) .style("stroke-dasharray", function(d) { if (d.style == "dashed") return "3, 3"; return "1, 0"; }); var g = svg.append("g").selectAll("g").data(graph.nodes).enter().append("g"); node = g.append("rect") // rounded corners (somewhat more expensive to render) .attr("rx", function(d) { return d.type == "module" ? 8 : 0; }); text = g.append("text") .attr("class", "labels") .style("font-size", "12px") .attr("fill", function(d) { return "#fff"; }) .attr("dy", ".35em") .attr("text-anchor", "middle") .text(function(d) { return d.name; }) .on("mouseover", fadeAnimated(minOpacity)) .on("mouseout", fadeAnimated(1)) .on("dblclick", openLink); var paddingLeftRight = 18; // adjust the padding values depending on font and font size var paddingTopBottom = 5; svg.selectAll("text").each(function(d, i) { var curPaddingLeftRight = paddingLeftRight; var curPaddingTopBottom = paddingTopBottom; if (graph.nodes[i].type == "module") { curPaddingLeftRight *= 1.5; curPaddingTopBottom *= 1.5; } // get bounding box of text field and store it graph.nodes[i].width = this.getBBox().width+curPaddingLeftRight; graph.nodes[i].height = this.getBBox().height+curPaddingTopBottom; graph.nodes[i].vx = 0; graph.nodes[i].vy = 0; }); simulation .nodes(graph.nodes) .on("tick", ticked); simulation.force("link") .links(graph.links); function ticked() { link .attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }); text .attr("x", function(d) { return d.x; }) .attr("y", function(d) { return d.y; }); svg.selectAll("rect") .attr("x", function(d) { return d.x - d.width/2; }) .attr("y", function(d) { return d.y - d.height/2; }) .attr("width", function(d) { return d.width; }) .attr("height", function(d) { return d.height; }) .attr("fill", function(d) { return d.color; }); } // open the 'node.url' attribute in a new tab, if it exists function openLink(n) { if (typeof n.url !== 'undefined') { window.open(n.url, '_blank'); } } // smooth fade in/out var animationTimer = null; var currentOpacity = 1; var destOpacity = 1; function fadeAnimated(opacity) { return function(d) { if (animationTimer != null) animationTimer.stop(); destOpacity = opacity; animationTimer = d3.interval(function(elapsed) { var newOpacity = currentOpacity + (destOpacity-currentOpacity) * elapsed/300; // check if we overshot the destination opacity if ((currentOpacity - destOpacity) * (newOpacity - destOpacity) < 0) { currentOpacity = destOpacity; } else { currentOpacity = newOpacity; } fade(currentOpacity)(d); if (Math.abs(currentOpacity - destOpacity) < 0.005) { animationTimer.stop(); animationTimer = null; } }, 30); } } // mouse over functionality: fade the rest of the graph var linkedByIndex = {}; graph.links.forEach(function(d) { linkedByIndex[d.source.index + "," + d.target.index] = 1; }); function isConnected(a, b) { return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index; } function fade(opacity) { return function(d) { /* The graph opacity is using the following behavior: * - no filtering (g_filterText == ""): * - mouse hovers over a node: the node and it's connected * nodes are visible, the rest is faded * - else: all nodes and links are visible * - filtering: * - all nodes matching the filter are always visible * - mouse hovers over a node: the connected nodes are visible * - no hovering: rest of the non-matching nodes are faded * (and all the links too) */ var invertedOpacity = (1+minOpacity) - opacity; if (g_filterText != "") { // in case of filtering, the default is to fade non-matching // nodes opacity = minOpacity; } function nodeOpacity(o) { if (g_filterText != "" && o.name.includes(g_filterText)) { thisOpacity = 1; // always visible if filter matches } else if (isConnected(d, o)) { if (g_filterText == "") { thisOpacity = 1; // connected w/o filtering -> show it } else if (d.name.includes(g_filterText)) { thisOpacity = invertedOpacity; } else { thisOpacity = opacity; } } else { thisOpacity = opacity; } this.setAttribute('fill-opacity', thisOpacity); return thisOpacity; } node.style("stroke-opacity", nodeOpacity); text.style("stroke-opacity", nodeOpacity); var linkOpacity = opacity; var linkOpacityConnected = 1; if (g_filterText != "") { if (d.name.includes(g_filterText)) { linkOpacityConnected = invertedOpacity; } else { linkOpacityConnected = minOpacity; } linkOpacity = minOpacity; } link.style("stroke-opacity", function(o) { return o.source === d || o.target === d ? linkOpacityConnected : linkOpacity; }); }; } }); } function reloadSimulation(e) { json_file_name = e.target.value; console.log(json_file_name); d3.selectAll("svg > *").remove(); loadSimulation(json_file_name); simulation.alpha(1).restart(); } /* initial graph */ loadSimulation(default_json_file); function rectCollide() { var nodes, sizes, masses; var size = constant([0, 0]); var strength = 0.3; var iterations = 20; function force() { var node, size, mass, xi, yi; var i = -1; while (++i < iterations) { iterate(); } function iterate() { var j = -1; var tree = d3.quadtree(nodes, xCenter, yCenter).visitAfter(prepare); while (++j < nodes.length) { node = nodes[j]; size = sizes[j]; mass = masses[j]; xi = xCenter(node); yi = yCenter(node); tree.visit(apply); } } function apply(quad, x0, y0, x1, y1) { var data = quad.data; var xSize = (size[0] + quad.size[0]) / 2; var ySize = (size[1] + quad.size[1]) / 2; if (data) { if (data.index <= node.index) { return; } var x = xi - xCenter(data); var y = yi - yCenter(data); var xd = Math.abs(x) - xSize; var yd = Math.abs(y) - ySize; if (xd < 0 && yd < 0) { var l = Math.sqrt(x * x + y * y); var m = masses[data.index] / (mass + masses[data.index]); if (l > 0.000001) { if (xd > yd) { node.vx -= (x *= xd / l * strength) * m; data.vx += x * (1 - m); } else { node.vy -= (y *= yd / l * strength) * m; data.vy += y * (1 - m); } } } } return x0 > xi + xSize || y0 > yi + ySize || x1 < xi - xSize || y1 < yi - ySize; } function prepare(quad) { if (quad.data) { quad.size = sizes[quad.data.index]; } else { quad.size = [0, 0]; var i = -1; while (++i < 4) { if (quad[i] && quad[i].size) { quad.size[0] = Math.max(quad.size[0], quad[i].size[0]); quad.size[1] = Math.max(quad.size[1], quad[i].size[1]); } } } } } function xCenter(d) { return d.x + d.vx; } function yCenter(d) { return d.y + d.vy; } force.initialize = function (_) { sizes = (nodes = _).map(size); masses = sizes.map(function (d) { return d[0] * d[1] }); } force.size = function (_) { return (arguments.length ? (size = typeof _ === 'function' ? _ : constant(_), force) : size); } force.strength = function (_) { return (arguments.length ? (strength = +_, force) : strength); } force.iterations = function (_) { return (arguments.length ? (iterations = +_, force) : iterations); } return force; } function boundedBox() { var nodes, sizes; var bounds; var size = constant([0, 0]); function force() { var node, size; var xi, x0, x1, yi, y0, y1; var i = -1; while (++i < nodes.length) { node = nodes[i]; size = sizes[i]; xi = node.x + node.vx; x0 = bounds[0][0] - (xi - size[0]/2); x1 = bounds[1][0] - (xi + size[0]/2); yi = node.y + node.vy; y0 = bounds[0][1] - (yi - size[1]/2); y1 = bounds[1][1] - (yi + size[1]/2); if (x0 > 0 || x1 < 0) { node.x += node.vx; node.vx = -node.vx; if (node.vx < x0) { node.x += x0 - node.vx; } if (node.vx > x1) { node.x += x1 - node.vx; } } if (y0 > 0 || y1 < 0) { node.y += node.vy; node.vy = -node.vy; if (node.vy < y0) { node.vy += y0 - node.vy; } if (node.vy > y1) { node.vy += y1 - node.vy; } } } } force.initialize = function (_) { sizes = (nodes = _).map(size); } force.bounds = function (_) { return (arguments.length ? (bounds = _, force) : bounds); } force.size = function (_) { return (arguments.length ? (size = typeof _ === 'function' ? _ : constant(_), force) : size); } return force; } function constant(_) { return function () { return _; } } } // initializeGraph() initializeGraph(); </script>

Graph Properties{#instructions}

The graph has the following properties:

  • Modules are shown in gray with rounded corners while topics are displayed as coloured rectangular boxes.
  • Associated modules and topics are connected by lines. Dashed lines indicate that the module publishes the topic, while solid lines indicate that the module subscribes to the topic.
  • Some modules and topics are excluded:
    • Topics that are subscribed/published by many modules: parameter_update, mavlink_log and log_message.
    • The set of logged topics.
    • Topics that have no subscriber or no publisher.
    • Modules in src/examples.
  • Hovering over a module/topic highlights all its connections.
  • Double-clicking on a topic opens its message definition.
  • Make sure your browser window is wide enough to display the full graph (the sidebar menu can be hidden with the icon in the top-left corner). You can also zoom the image.
  • The Preset selection list allows you to refine the list of modules that are shown.
  • The Search box can be used to find particular modules/topics (topics that are not selected by the search are greyed-out).