mirror of https://github.com/sbt/sbt.git
Merge pull request #7301 from sideeffffect/dependency-graph-colors-backport
Dependency graph with colors (based on the organization) (backport)
This commit is contained in:
commit
84781c2dbe
|
|
@ -30,86 +30,15 @@ THE SOFTWARE.
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Dependency Graph</title>
|
<title>Dependency Graph</title>
|
||||||
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
|
<script src="https://d3js.org/d3.v5.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
<script src="https://unpkg.com/@hpcc-js/wasm@0.3.15/dist/index.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/dagre-d3/0.4.16/dagre-d3.min.js"></script>
|
<script src="https://unpkg.com/d3-graphviz@3.2.0/build/d3-graphviz.js"></script>
|
||||||
<script src="https://dagrejs.github.io/project/graphlib-dot/v0.6.3/graphlib-dot.min.js"></script>
|
|
||||||
<script src="dependencies.dot.js"></script>
|
<script src="dependencies.dot.js"></script>
|
||||||
|
|
||||||
<style>
|
<body>
|
||||||
body {
|
<div id="graph"></div>
|
||||||
margin: 0;
|
</body>
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.node {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node rect,
|
|
||||||
.node circle,
|
|
||||||
.node ellipse {
|
|
||||||
stroke: #333;
|
|
||||||
fill: #fff;
|
|
||||||
stroke-width: 1.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cluster rect {
|
|
||||||
stroke: #333;
|
|
||||||
fill: #000;
|
|
||||||
fill-opacity: 0.1;
|
|
||||||
stroke-width: 1.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edgePath path.path {
|
|
||||||
stroke: #333;
|
|
||||||
stroke-width: 1.5px;
|
|
||||||
fill: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
h1, h2 {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<body onLoad="initialize()">
|
|
||||||
|
|
||||||
<svg width=1280 height=1024>
|
|
||||||
<g/>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function initialize() {
|
d3.select("#graph").graphviz().renderDot(decodeURIComponent(data));
|
||||||
// Set up zoom support
|
|
||||||
var svg = d3.select("svg"),
|
|
||||||
inner = d3.select("svg g"),
|
|
||||||
zoom = d3.behavior.zoom().on("zoom", function() {
|
|
||||||
inner.attr("transform", "translate(" + d3.event.translate + ")" +
|
|
||||||
"scale(" + d3.event.scale + ")");
|
|
||||||
});
|
|
||||||
svg.attr("width", window.innerWidth);
|
|
||||||
|
|
||||||
svg.call(zoom);
|
|
||||||
// Create and configure the renderer
|
|
||||||
var render = dagreD3.render();
|
|
||||||
function tryDraw(inputGraph) {
|
|
||||||
var g;
|
|
||||||
{
|
|
||||||
g = graphlibDot.read(inputGraph);
|
|
||||||
g.graph().rankdir = "LR";
|
|
||||||
d3.select("svg g").call(render, g);
|
|
||||||
|
|
||||||
// Center the graph
|
|
||||||
var initialScale = 0.10;
|
|
||||||
zoom
|
|
||||||
.translate([(svg.attr("width") - g.graph().width * initialScale) / 2, 20])
|
|
||||||
.scale(initialScale)
|
|
||||||
.event(svg);
|
|
||||||
svg.attr('height', g.graph().height * initialScale + 40);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tryDraw(decodeURIComponent(data));
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
|
||||||
|
|
|
||||||
|
|
@ -11,23 +11,38 @@ package graph
|
||||||
package rendering
|
package rendering
|
||||||
|
|
||||||
object DOT {
|
object DOT {
|
||||||
val EvictedStyle = "stroke-dasharray: 5,5"
|
val EvictedStyle = "dashed"
|
||||||
|
|
||||||
def dotGraph(
|
def dotGraph(
|
||||||
graph: ModuleGraph,
|
graph: ModuleGraph,
|
||||||
dotHead: String,
|
dotHead: String,
|
||||||
nodeFormation: (String, String, String) => String,
|
nodeFormation: (String, String, String) => String,
|
||||||
labelRendering: HTMLLabelRendering
|
labelRendering: HTMLLabelRendering,
|
||||||
|
colors: Boolean
|
||||||
): String = {
|
): String = {
|
||||||
val nodes = {
|
val nodes = {
|
||||||
for (n <- graph.nodes) yield {
|
for (n <- graph.nodes) yield {
|
||||||
val style = if (n.isEvicted) EvictedStyle else ""
|
|
||||||
val label = nodeFormation(n.id.organization, n.id.name, n.id.version)
|
val label = nodeFormation(n.id.organization, n.id.name, n.id.version)
|
||||||
""" "%s"[%s style="%s"]""".format(
|
val style = if (n.isEvicted) EvictedStyle else ""
|
||||||
n.id.idString,
|
val penwidth = if (n.isEvicted) "3" else "5"
|
||||||
labelRendering.renderLabel(label),
|
val color = if (colors) {
|
||||||
style
|
val orgHash = n.id.organization.hashCode
|
||||||
)
|
val r = (orgHash >> 16) & 0xFF
|
||||||
|
val g = (orgHash >> 8) & 0xFF
|
||||||
|
val b = (orgHash >> 0) & 0xFF
|
||||||
|
val r1 = (r * 0.90).toInt
|
||||||
|
val g1 = (g * 0.90).toInt
|
||||||
|
val b1 = (b * 0.90).toInt
|
||||||
|
(r1 << 16) | (g1 << 8) | (b1 << 0)
|
||||||
|
} else 0
|
||||||
|
s""" "%s"[shape=box %s style="%s" penwidth="%s" color="%s"]"""
|
||||||
|
.format(
|
||||||
|
n.id.idString,
|
||||||
|
labelRendering.renderLabel(label),
|
||||||
|
style,
|
||||||
|
penwidth,
|
||||||
|
f"#$color%06X",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}.sorted.mkString("\n")
|
}.sorted.mkString("\n")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,9 @@ abstract class DependencyTreeKeys {
|
||||||
taskKey[File]("Creates a graphml file containing the dependency-graph for a project")
|
taskKey[File]("Creates a graphml file containing the dependency-graph for a project")
|
||||||
val dependencyDotFile =
|
val dependencyDotFile =
|
||||||
settingKey[File]("The location the dot file should be generated at")
|
settingKey[File]("The location the dot file should be generated at")
|
||||||
|
val dependencyDotNodeColors = settingKey[Boolean](
|
||||||
|
"The boxes of nodes are painted with colors. Otherwise they're black."
|
||||||
|
)
|
||||||
val dependencyDotNodeLabel = settingKey[(String, String, String) => String](
|
val dependencyDotNodeLabel = settingKey[(String, String, String) => String](
|
||||||
"Returns a formated string of a dependency. Takes organization, name and version as parameters"
|
"Returns a formated string of a dependency. Takes organization, name and version as parameters"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -109,15 +109,17 @@ object DependencyTreeSettings {
|
||||||
dependencyTreeModuleGraph0.value,
|
dependencyTreeModuleGraph0.value,
|
||||||
dependencyDotHeader.value,
|
dependencyDotHeader.value,
|
||||||
dependencyDotNodeLabel.value,
|
dependencyDotNodeLabel.value,
|
||||||
rendering.DOT.AngleBrackets
|
rendering.DOT.AngleBrackets,
|
||||||
|
dependencyDotNodeColors.value
|
||||||
),
|
),
|
||||||
dependencyDot := writeToFile(dependencyDot / asString, dependencyDotFile).value,
|
dependencyDot := writeToFile(dependencyDot / asString, dependencyDotFile).value,
|
||||||
dependencyDotHeader :=
|
dependencyDotHeader :=
|
||||||
"""|digraph "dependency-graph" {
|
"""|digraph "dependency-graph" {
|
||||||
| graph[rankdir="LR"]
|
| graph[rankdir="LR"; splines=polyline]
|
||||||
| edge [
|
| edge [
|
||||||
| arrowtail="none"
|
| arrowtail="none"
|
||||||
| ]""".stripMargin,
|
| ]""".stripMargin,
|
||||||
|
dependencyDotNodeColors := true,
|
||||||
dependencyDotNodeLabel := { (organization: String, name: String, version: String) =>
|
dependencyDotNodeLabel := { (organization: String, name: String, version: String) =>
|
||||||
"""%s<BR/><B>%s</B><BR/>%s""".format(organization, name, version)
|
"""%s<BR/><B>%s</B><BR/>%s""".format(organization, name, version)
|
||||||
},
|
},
|
||||||
|
|
@ -192,7 +194,8 @@ object DependencyTreeSettings {
|
||||||
graph,
|
graph,
|
||||||
dependencyDotHeader.value,
|
dependencyDotHeader.value,
|
||||||
dependencyDotNodeLabel.value,
|
dependencyDotNodeLabel.value,
|
||||||
rendering.DOT.LabelTypeHtml
|
rendering.DOT.AngleBrackets,
|
||||||
|
dependencyDotNodeColors.value
|
||||||
)
|
)
|
||||||
val link = DagreHTML.createLink(dotGraph, target.value)
|
val link = DagreHTML.createLink(dotGraph, target.value)
|
||||||
streams.value.log.info(s"HTML graph written to $link")
|
streams.value.log.info(s"HTML graph written to $link")
|
||||||
|
|
|
||||||
|
|
@ -11,43 +11,43 @@ lazy val justATransitiveDependencyProject = project
|
||||||
lazy val justADependencyProject = project
|
lazy val justADependencyProject = project
|
||||||
|
|
||||||
lazy val test_project = project
|
lazy val test_project = project
|
||||||
.dependsOn(justADependencyProject, justATransitiveDependencyProject)
|
.dependsOn(justADependencyProject, justATransitiveDependencyProject)
|
||||||
.settings(
|
.settings(
|
||||||
TaskKey[Unit]("check") := {
|
TaskKey[Unit]("check") := {
|
||||||
val dotFile = (dependencyDot in Compile).value
|
val dotFile = (dependencyDot in Compile).value
|
||||||
val expectedGraph =
|
val expectedGraph =
|
||||||
"""digraph "dependency-graph" {
|
"""digraph "dependency-graph" {
|
||||||
| graph[rankdir="LR"]
|
| graph[rankdir="LR"; splines=polyline]
|
||||||
| edge [
|
| edge [
|
||||||
| arrowtail="none"
|
| arrowtail="none"
|
||||||
| ]
|
| ]
|
||||||
| "justadependencyproject:justadependencyproject_2.9.2:0.1-SNAPSHOT"[label=<justadependencyproject<BR/><B>justadependencyproject_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
|
| "justadependencyproject:justadependencyproject_2.9.2:0.1-SNAPSHOT"[shape=box label=<justadependencyproject<BR/><B>justadependencyproject_2.9.2</B><BR/>0.1-SNAPSHOT> style="" penwidth="5" color="#B6E316"]
|
||||||
| "justatransitivedependencyproject:justatransitivedependencyproject_2.9.2:0.1-SNAPSHOT"[label=<justatransitivedependencyproject<BR/><B>justatransitivedependencyproject_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
|
| "justatransitivedependencyproject:justatransitivedependencyproject_2.9.2:0.1-SNAPSHOT"[shape=box label=<justatransitivedependencyproject<BR/><B>justatransitivedependencyproject_2.9.2</B><BR/>0.1-SNAPSHOT> style="" penwidth="5" color="#0E92BE"]
|
||||||
| "justatransivitedependencyendpointproject:justatransivitedependencyendpointproject_2.9.2:0.1-SNAPSHOT"[label=<justatransivitedependencyendpointproject<BR/><B>justatransivitedependencyendpointproject_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
|
| "justatransivitedependencyendpointproject:justatransivitedependencyendpointproject_2.9.2:0.1-SNAPSHOT"[shape=box label=<justatransivitedependencyendpointproject<BR/><B>justatransivitedependencyendpointproject_2.9.2</B><BR/>0.1-SNAPSHOT> style="" penwidth="5" color="#9EAD1B"]
|
||||||
| "test_project:test_project_2.9.2:0.1-SNAPSHOT"[label=<test_project<BR/><B>test_project_2.9.2</B><BR/>0.1-SNAPSHOT> style=""]
|
| "test_project:test_project_2.9.2:0.1-SNAPSHOT"[shape=box label=<test_project<BR/><B>test_project_2.9.2</B><BR/>0.1-SNAPSHOT> style="" penwidth="5" color="#C37661"]
|
||||||
| "justatransitivedependencyproject:justatransitivedependencyproject_2.9.2:0.1-SNAPSHOT" -> "justatransivitedependencyendpointproject:justatransivitedependencyendpointproject_2.9.2:0.1-SNAPSHOT"
|
| "justatransitivedependencyproject:justatransitivedependencyproject_2.9.2:0.1-SNAPSHOT" -> "justatransivitedependencyendpointproject:justatransivitedependencyendpointproject_2.9.2:0.1-SNAPSHOT"
|
||||||
| "test_project:test_project_2.9.2:0.1-SNAPSHOT" -> "justadependencyproject:justadependencyproject_2.9.2:0.1-SNAPSHOT"
|
| "test_project:test_project_2.9.2:0.1-SNAPSHOT" -> "justadependencyproject:justadependencyproject_2.9.2:0.1-SNAPSHOT"
|
||||||
| "test_project:test_project_2.9.2:0.1-SNAPSHOT" -> "justatransitivedependencyproject:justatransitivedependencyproject_2.9.2:0.1-SNAPSHOT"
|
| "test_project:test_project_2.9.2:0.1-SNAPSHOT" -> "justatransitivedependencyproject:justatransitivedependencyproject_2.9.2:0.1-SNAPSHOT"
|
||||||
|}
|
|}
|
||||||
""".stripMargin
|
""".stripMargin
|
||||||
|
|
||||||
val graph : String = scala.io.Source.fromFile(dotFile.getAbsolutePath).mkString
|
val graph: String = scala.io.Source.fromFile(dotFile.getAbsolutePath).mkString
|
||||||
val errors = compareByLine(graph, expectedGraph)
|
val errors = compareByLine(graph, expectedGraph)
|
||||||
require(errors.isEmpty , errors.mkString("\n"))
|
require(errors.isEmpty, errors.mkString("\n"))
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def compareByLine(got : String, expected : String) : Seq[String] = {
|
def compareByLine(got: String, expected: String): Seq[String] = {
|
||||||
val errors = ListBuffer[String]()
|
val errors = ListBuffer[String]()
|
||||||
got.split("\n").zip(expected.split("\n").toSeq).zipWithIndex.foreach { case((got_line : String, expected_line : String), i : Int) =>
|
got.split("\n").zip(expected.split("\n").toSeq).zipWithIndex.foreach {
|
||||||
if(got_line != expected_line) {
|
case ((got_line: String, expected_line: String), i: Int) =>
|
||||||
errors.append(
|
if (got_line != expected_line) {
|
||||||
"""not matching lines at line %s
|
errors.append("""not matching lines at line %s
|
||||||
|expected: %s
|
|expected: %s
|
||||||
|got: %s
|
|got: %s
|
||||||
|""".stripMargin.format(i,expected_line, got_line))
|
|""".stripMargin.format(i, expected_line, got_line))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
errors
|
errors
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,146 +11,75 @@ lazy val justATransitiveDependencyProject = project
|
||||||
lazy val justADependencyProject = project
|
lazy val justADependencyProject = project
|
||||||
|
|
||||||
lazy val test_project = project
|
lazy val test_project = project
|
||||||
.dependsOn(justADependencyProject, justATransitiveDependencyProject)
|
.dependsOn(justADependencyProject, justATransitiveDependencyProject)
|
||||||
.settings(
|
.settings(
|
||||||
TaskKey[Unit]("check") := {
|
TaskKey[Unit]("check") := {
|
||||||
val htmlFile = (dependencyBrowseGraphHTML in Compile).value
|
val htmlFile = (dependencyBrowseGraphHTML in Compile).value
|
||||||
val expectedHtml =
|
val expectedHtml =
|
||||||
"""<!doctype html>
|
"""<!doctype html>
|
||||||
|
|
|
|
||||||
|<!--
|
|<!--
|
||||||
|
|
|
|
||||||
|Based on https://github.com/cpettitt/dagre-d3/blob/d215446e7e40ebfca303f4733e746e96420e3b46/demo/interactive-demo.html
|
|Based on https://github.com/cpettitt/dagre-d3/blob/d215446e7e40ebfca303f4733e746e96420e3b46/demo/interactive-demo.html
|
||||||
|which is published under this license:
|
|which is published under this license:
|
||||||
|
|
|
|
||||||
|Copyright (c) 2013 Chris Pettitt
|
|Copyright (c) 2013 Chris Pettitt
|
||||||
|
|
|
|
||||||
|Permission is hereby granted, free of charge, to any person obtaining a copy
|
|Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|of this software and associated documentation files (the "Software"), to deal
|
|of this software and associated documentation files (the "Software"), to deal
|
||||||
|in the Software without restriction, including without limitation the rights
|
|in the Software without restriction, including without limitation the rights
|
||||||
|to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|copies of the Software, and to permit persons to whom the Software is
|
|copies of the Software, and to permit persons to whom the Software is
|
||||||
|furnished to do so, subject to the following conditions:
|
|furnished to do so, subject to the following conditions:
|
||||||
|
|
|
|
||||||
|The above copyright notice and this permission notice shall be included in
|
|The above copyright notice and this permission notice shall be included in
|
||||||
|all copies or substantial portions of the Software.
|
|all copies or substantial portions of the Software.
|
||||||
|
|
|
|
||||||
|THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|THE SOFTWARE.
|
|THE SOFTWARE.
|
||||||
|
|
|
|
||||||
|-->
|
|-->
|
||||||
|
|
|
|
||||||
|<meta charset="utf-8">
|
|<meta charset="utf-8">
|
||||||
|<title>Dependency Graph</title>
|
|<title>Dependency Graph</title>
|
||||||
|
|
|
|
||||||
|<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
|
|<script src="https://d3js.org/d3.v5.min.js"></script>
|
||||||
|<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
|<script src="https://unpkg.com/@hpcc-js/wasm@0.3.15/dist/index.min.js"></script>
|
||||||
|<script src="https://cdnjs.cloudflare.com/ajax/libs/dagre-d3/0.4.16/dagre-d3.min.js"></script>
|
|<script src="https://unpkg.com/d3-graphviz@3.2.0/build/d3-graphviz.js"></script>
|
||||||
|<script src="https://dagrejs.github.io/project/graphlib-dot/v0.6.3/graphlib-dot.min.js"></script>
|
|<script src="dependencies.dot.js"></script>
|
||||||
|<script src="dependencies.dot.js"></script>
|
|
|
||||||
|
|
|<body>
|
||||||
|<style>
|
|<div id="graph"></div>
|
||||||
| body {
|
|</body>
|
||||||
| margin: 0;
|
|
|
||||||
| overflow: hidden;
|
|<script>
|
||||||
| }
|
| d3.select("#graph").graphviz().renderDot(decodeURIComponent(data));
|
||||||
| .node {
|
|</script>
|
||||||
| white-space: nowrap;
|
|
|
||||||
| }
|
|
||||||
|
|
|
||||||
| .node rect,
|
|
||||||
| .node circle,
|
|
||||||
| .node ellipse {
|
|
||||||
| stroke: #333;
|
|
||||||
| fill: #fff;
|
|
||||||
| stroke-width: 1.5px;
|
|
||||||
| }
|
|
||||||
|
|
|
||||||
| .cluster rect {
|
|
||||||
| stroke: #333;
|
|
||||||
| fill: #000;
|
|
||||||
| fill-opacity: 0.1;
|
|
||||||
| stroke-width: 1.5px;
|
|
||||||
| }
|
|
||||||
|
|
|
||||||
| .edgePath path.path {
|
|
||||||
| stroke: #333;
|
|
||||||
| stroke-width: 1.5px;
|
|
||||||
| fill: none;
|
|
||||||
| }
|
|
||||||
|</style>
|
|
||||||
|
|
|
||||||
|<style>
|
|
||||||
| h1, h2 {
|
|
||||||
| color: #333;
|
|
||||||
| }
|
|
||||||
|</style>
|
|
||||||
|
|
|
||||||
|<body onLoad="initialize()">
|
|
||||||
|
|
|
||||||
|<svg width=1280 height=1024>
|
|
||||||
| <g/>
|
|
||||||
|</svg>
|
|
||||||
|
|
|
||||||
|<script>
|
|
||||||
|function initialize() {
|
|
||||||
| // Set up zoom support
|
|
||||||
| var svg = d3.select("svg"),
|
|
||||||
| inner = d3.select("svg g"),
|
|
||||||
| zoom = d3.behavior.zoom().on("zoom", function() {
|
|
||||||
| inner.attr("transform", "translate(" + d3.event.translate + ")" +
|
|
||||||
| "scale(" + d3.event.scale + ")");
|
|
||||||
| });
|
|
||||||
| svg.attr("width", window.innerWidth);
|
|
||||||
|
|
|
||||||
| svg.call(zoom);
|
|
||||||
| // Create and configure the renderer
|
|
||||||
| var render = dagreD3.render();
|
|
||||||
| function tryDraw(inputGraph) {
|
|
||||||
| var g;
|
|
||||||
| {
|
|
||||||
| g = graphlibDot.read(inputGraph);
|
|
||||||
| g.graph().rankdir = "LR";
|
|
||||||
| d3.select("svg g").call(render, g);
|
|
||||||
|
|
|
||||||
| // Center the graph
|
|
||||||
| var initialScale = 0.10;
|
|
||||||
| zoom
|
|
||||||
| .translate([(svg.attr("width") - g.graph().width * initialScale) / 2, 20])
|
|
||||||
| .scale(initialScale)
|
|
||||||
| .event(svg);
|
|
||||||
| svg.attr('height', g.graph().height * initialScale + 40);
|
|
||||||
| }
|
|
||||||
| }
|
|
||||||
| tryDraw(decodeURIComponent(data));
|
|
||||||
|}
|
|
||||||
|</script>
|
|
||||||
|</body>
|
|
||||||
|
|
|
||||||
""".stripMargin
|
""".stripMargin
|
||||||
|
|
||||||
val html : String = scala.io.Source.fromFile(htmlFile).mkString
|
val html: String = scala.io.Source.fromFile(htmlFile).mkString
|
||||||
val errors = compareByLine(html, expectedHtml)
|
val errors = compareByLine(html, expectedHtml)
|
||||||
require(errors.isEmpty , errors.mkString("\n"))
|
require(errors.isEmpty, errors.mkString("\n"))
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def compareByLine(got : String, expected : String) : Seq[String] = {
|
def compareByLine(got: String, expected: String): Seq[String] = {
|
||||||
val errors = ListBuffer[String]()
|
val errors = ListBuffer[String]()
|
||||||
got.split("\n").zip(expected.split("\n").toSeq).zipWithIndex.foreach { case((got_line : String, expected_line : String), i : Int) =>
|
got.split("\n").zip(expected.split("\n").toSeq).zipWithIndex.foreach {
|
||||||
if(got_line != expected_line) {
|
case ((got_line: String, expected_line: String), i: Int) =>
|
||||||
errors.append(
|
if (got_line != expected_line) {
|
||||||
"""not matching lines at line %s
|
errors.append("""not matching lines at line %s
|
||||||
|expected: %s
|
|expected: %s
|
||||||
|got: %s
|
|got: %s
|
||||||
|""".stripMargin.format(i,expected_line, got_line))
|
|""".stripMargin.format(i, expected_line, got_line))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
errors
|
errors
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue