From 6699fe49fa3137719f1d22bec14ac76b010f54ca Mon Sep 17 00:00:00 2001 From: Andrzej Ratajczak Date: Mon, 21 Dec 2020 12:14:55 +0100 Subject: [PATCH] Fix position of hirerarchy diagram, highlight main node, add button to reset zoom --- scala3doc/resources/dotty_res/scripts/ux.js | 56 ++++++++++++++++++- .../resources/dotty_res/styles/diagram.css | 25 ++++++++- .../resources/dotty_res/styles/scalastyle.css | 1 + .../dotty/renderers/DotDiagramBuilder.scala | 2 +- .../src/dotty/renderers/MemberRenderer.scala | 5 +- scala3doc/src/dotty/renderers/html.scala | 2 + 6 files changed, 85 insertions(+), 6 deletions(-) diff --git a/scala3doc/resources/dotty_res/scripts/ux.js b/scala3doc/resources/dotty_res/scripts/ux.js index 3778cbc31c9a..92fa00932ae3 100644 --- a/scala3doc/resources/dotty_res/scripts/ux.js +++ b/scala3doc/resources/dotty_res/scripts/ux.js @@ -55,15 +55,22 @@ window.addEventListener("DOMContentLoaded", () => { hljs.initHighlighting(); }); +var zoom; +var transform; + function showGraph() { if ($("svg#graph").children().length == 0) { var dotNode = document.querySelector("#dot") if (dotNode){ var svg = d3.select("#graph"); + var radialGradient = svg.append("defs").append("radialGradient").attr("id", "Gradient"); + radialGradient.append("stop").attr("stop-color", "#ffd47f").attr("offset", "20%"); + radialGradient.append("stop").attr("stop-color", "white").attr("offset", "100%"); + var inner = svg.append("g"); // Set up zoom support - var zoom = d3.zoom() + zoom = d3.zoom() .on("zoom", function({transform}) { inner.attr("transform", transform); }); @@ -76,15 +83,60 @@ function showGraph() { g.setNode(v, { labelType: "html", label: g.node(v).label, - style: g.node(v).style + style: g.node(v).style, + id: g.node(v).id }); }); + g.setNode("node0Cluster", { + style: "fill: url(#Gradient);", + id: "node0Cluster" + }); + g.setParent("node0", "node0Cluster"); + g.edges().forEach(function(v) { g.setEdge(v, { arrowhead: "vee" }); }); render(inner, g); + + // Set the 'fit to content graph' upon landing on the page + var bounds = svg.node().getBBox(); + var parent = svg.node().parentElement; + var fullWidth = parent.clientWidth || parent.parentNode.clientWidth, + fullHeight = parent.clientHeight || parent.parentNode.clientHeight; + var width = bounds.width, + height = bounds.height; + var midX = bounds.x + width / 2, + midY = bounds.y + height / 2; + if (width == 0 || height == 0) return; // nothing to fit + var scale = Math.min(fullWidth / width, fullHeight / height) * 0.99; // 0.99 to make a little padding + var translate = [fullWidth / 2 - scale * midX, fullHeight / 2 - scale * midY]; + + transform = d3.zoomIdentity + .translate(translate[0], translate[1]) + .scale(scale); + + svg.call(zoom.transform, transform); + + // This is nasty hack to prevent DagreD3 from stretching cluster. There is similar issue on github since October 2019, but haven't been answered yet. https://github.com/dagrejs/dagre-d3/issues/377 + var node0 = d3.select("g#node0")._groups[0][0]; + var node0Rect = node0.children[0]; + var node0Cluster = d3.select("g#node0Cluster")._groups[0][0]; + var node0ClusterRect = node0Cluster.children[0]; + node0Cluster.setAttribute("transform", node0.getAttribute("transform")); + node0ClusterRect.setAttribute("width", +node0Rect.getAttribute("width") + 80); + node0ClusterRect.setAttribute("height", +node0Rect.getAttribute("height") + 80); + node0ClusterRect.setAttribute("x", node0Rect.getAttribute("x") - 40); + node0ClusterRect.setAttribute("y", node0Rect.getAttribute("y") - 40); } } } + +function zoomOut() { + var svg = d3.select("#graph"); + svg + .transition() + .duration(2000) + .call(zoom.transform, transform); +} diff --git a/scala3doc/resources/dotty_res/styles/diagram.css b/scala3doc/resources/dotty_res/styles/diagram.css index 89a6e9da4b6c..1960ad9dcba7 100644 --- a/scala3doc/resources/dotty_res/styles/diagram.css +++ b/scala3doc/resources/dotty_res/styles/diagram.css @@ -16,7 +16,7 @@ #graph { width: 100%; - height: 80%; + height: 400px; } .diagram-class a { @@ -29,3 +29,26 @@ .diagram-class span[data-unresolved-link] { color: #FFF; } + +.btn { + padding: 8px 16px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + margin: 4px 2px; + transition-duration: 0.4s; + cursor: pointer; + background-color: white; + color: black; + border: 2px solid #003048; + position: absolute; + top: 0; + left: 0; + z-index:2; +} + +.btn:hover { + background-color: #003048; + color: white; +} diff --git a/scala3doc/resources/dotty_res/styles/scalastyle.css b/scala3doc/resources/dotty_res/styles/scalastyle.css index 9b703f197c42..b7417d7df9e9 100644 --- a/scala3doc/resources/dotty_res/styles/scalastyle.css +++ b/scala3doc/resources/dotty_res/styles/scalastyle.css @@ -610,6 +610,7 @@ footer .pull-right { .diagram-class { width: 100%; max-height: 400px; + position: relative; } /* Large Screens */ diff --git a/scala3doc/src/dotty/renderers/DotDiagramBuilder.scala b/scala3doc/src/dotty/renderers/DotDiagramBuilder.scala index bebd8f3bb82b..2171f21e0a2a 100644 --- a/scala3doc/src/dotty/renderers/DotDiagramBuilder.scala +++ b/scala3doc/src/dotty/renderers/DotDiagramBuilder.scala @@ -19,7 +19,7 @@ object DotDiagramBuilder: val vWithId = diagram.verteciesWithId val vertecies = vWithId.map { (vertex, id) => - s"""node${id} [label="${getHtmlLabel(vertex, renderer)}", style="${getStyle(vertex)}"];\n""" + s"""node${id} [id=node${id}, label="${getHtmlLabel(vertex, renderer)}", style="${getStyle(vertex)}"];\n""" }.mkString val edges = diagram.edges.map { (from, to) => diff --git a/scala3doc/src/dotty/renderers/MemberRenderer.scala b/scala3doc/src/dotty/renderers/MemberRenderer.scala index 95f7eef82350..19067eeede48 100644 --- a/scala3doc/src/dotty/renderers/MemberRenderer.scala +++ b/scala3doc/src/dotty/renderers/MemberRenderer.scala @@ -315,6 +315,7 @@ class MemberRenderer(signatureRenderer: SignatureRenderer, buildNode: ContentNod val graphHtml = MemberExtension.getFrom(m).map(_.graph) match case Some(graph) if graph.edges.nonEmpty => Seq(div( id := "inheritance-diagram", cls := "diagram-class showGraph")( + input(value := "Reset zoom", `type` := "button", cls := "btn", onclick := "zoomOut()"), svg(id := "graph"), script(`type` := "text/dot", id := "dot")( raw(DotDiagramBuilder.build(graph, signatureRenderer)) @@ -334,8 +335,8 @@ class MemberRenderer(signatureRenderer: SignatureRenderer, buildNode: ContentNod renderTabs( singleSelection = true, Tab("Graph", "graph", graphHtml, "showGraph"), - Tab("Super types", "supertypes", supertypes), - Tab("Known subtyes", "subtypes",subtypes), + Tab("Supertypes", "supertypes", supertypes), + Tab("Known subtypes", "subtypes", subtypes), ) private def buildDocumentableFilter = div(cls := "documentableFilter")( diff --git a/scala3doc/src/dotty/renderers/html.scala b/scala3doc/src/dotty/renderers/html.scala index 12701491c03a..a3e29b056161 100644 --- a/scala3doc/src/dotty/renderers/html.scala +++ b/scala3doc/src/dotty/renderers/html.scala @@ -96,6 +96,8 @@ object HTML: val content = Attr("content") val testId = Attr("data-test-id") val alt = Attr("alt") + val value = Attr("value") + val onclick=Attr("onclick") def raw(content: String): AppliedTag = new AppliedTag(content) def raw(content: StringBuilder): AppliedTag = content