{#include main fluid=true} {#script} // Build step dependency graph built with d3.js // Based on https://observablehq.com/@d3/mobile-patent-suits const stepId = "{currentRequest.getParam('stepId')}"; const nodes = [ {#each info:buildMetrics.get.dependencyGraphs.get(currentRequest.getParam('stepId')).nodes} { id:"{it.stepId}", root:{#if it.stepId == currentRequest.getParam('stepId')}true{#else}false{/if}, simpleName:"{it.simpleName}", encodedId:"{it.encodedStepId}" }, {/each} ]; const links = [ {#each info:buildMetrics.get.dependencyGraphs.get(currentRequest.getParam('stepId')).links} { source:"{it.source}", target:"{it.target}", type:"{it.type}" }, {/each} ]; {| const types = ['directDependency','directDependent','dependency']; const height = 600; const width = 1200; const color = d3.scaleOrdinal(types, d3.schemeCategory10); // Legend colors const legendDirectDependency = document.querySelector(".legend-direct-dependency"); legendDirectDependency.style.color = color('directDependency'); const legendDirectDependent = document.querySelector(".legend-direct-dependent"); legendDirectDependent.style.color = color('directDependent'); // const legendDependency = document.querySelector(".legend-dependency"); // legendDependency.style.color = color('dependency'); function linkArc(d) { const r = Math.hypot(d.target.x - d.source.x, d.target.y - d.source.y); return ` M${d.source.x},${d.source.y} A${r},${r} 0 0,1 ${d.target.x},${d.target.y} `; } const simulation = d3.forceSimulation(nodes) .force("link", d3.forceLink(links).id(d => d.id).distance(function(d) { return d.source.id === stepId || d.type === 'directDependency' ? 125 : 255; })) .force("charge", d3.forceManyBody().strength(-400)) .force("x", d3.forceX()) .force("y", d3.forceY()); function dragstart(event, d){ // this line is needed, otherwise the simulation stops after few seconds if (!event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; }; function dragged(event, d) { d.fx = event.x; d.fy = event.y; } function dragended(event, d) { d.fx = event.x; d.fy = event.y; } const svg = d3.select("#buildStepDepGraph_area") .attr("viewBox", [-width / 3, -height / 3, width, height]) .style("font", "11px sans-serif"); svg.append("defs").selectAll("marker") .data(types) .join("marker") .attr("id", d => `arrow-${d}`) .attr("viewBox", "0 -5 10 10") .attr("refX", 15) .attr("refY", -0.5) .attr("markerWidth", 6) .attr("markerHeight", 6) .attr("orient", "auto") .append("path") .attr("fill", color) .attr("d", "M0,-5L10,0L0,5"); const link = svg.append("g") .attr("fill", "none") .attr("stroke-width", 1.5) .selectAll("path") .data(links) .join("path") .attr("stroke", d => color(d.type)) .attr("marker-end", d => `url(${new URL(`#arrow-${d.type}`, location)})`); const node = svg.append("g") .attr("fill", "currentColor") .attr("stroke-linecap", "round") .attr("stroke-linejoin", "round") .selectAll("g") .data(nodes) .join("g") .call(d3.drag().on("drag", dragged).on("end", dragended).on("start", dragstart)); node.append("circle") .attr("stroke", "white") .attr("stroke-width", 1) .attr("r", 5) .style("fill", d => d.root ? "red" : "black"); const anchor = node.append("a") .attr("xlink:href", d => "build-step-dependency-graph?stepId=" + d.encodedId); anchor.append("svg:text") .attr("x", 8) .attr("y", "0.31em") .style("fill", "#1f77b4") .text(d => d.simpleName); anchor.append("svg:title") .text(d => d.id); simulation.on("tick", () => { link.attr("d", linkArc); node.attr("transform", d => `translate(${d.x},${d.y})`); }); |} {#breadcrumbs} Build Steps{/breadcrumbs} {#title}Build Step Dependency Graph{/title} {#body}
{currentRequest.getParam('stepId')}
{/body} {#scriptref} {/scriptref} {/include}