This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/master by this push:
new f2e2d79e4b CAUSEWAY-3565: d3js graph renderer: support node highlight
toggle
f2e2d79e4b is described below
commit f2e2d79e4b619a1498bcb05e81b97aa720d7256a
Author: andi-huber <[email protected]>
AuthorDate: Sat Sep 9 08:15:12 2023 +0200
CAUSEWAY-3565: d3js graph renderer: support node highlight toggle
---
.../objgraph/d3js/ObjectGraphRendererD3js.java | 24 ++---
.../objgraph/d3js/assets/force-directed-graph.css | 1 -
.../objgraph/d3js/assets/force-directed-graph.js | 112 ++++++++++++++-------
3 files changed, 87 insertions(+), 50 deletions(-)
diff --git
a/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/d3js/ObjectGraphRendererD3js.java
b/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/d3js/ObjectGraphRendererD3js.java
index 07959e5308..775e44b986 100644
---
a/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/d3js/ObjectGraphRendererD3js.java
+++
b/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/d3js/ObjectGraphRendererD3js.java
@@ -88,21 +88,15 @@ public class ObjectGraphRendererD3js implements
ObjectGraph.Renderer {
val d3jsGraph = new D3jsGraph(new ArrayList<>(), new ArrayList<>());
-//debug
-// d3jsGraph.nodes.add(new D3jsGraph.Node(1, "A", "an A"));
-// d3jsGraph.nodes.add(new D3jsGraph.Node(2, "B", "a B"));
-// d3jsGraph.nodes.add(new D3jsGraph.Node(3, "C", "a C"));
-//
-// d3jsGraph.links.add(new D3jsGraph.Link(1, 2, "E1"));
-// d3jsGraph.links.add(new D3jsGraph.Link(2, 3, "E2"));
-// d3jsGraph.links.add(new D3jsGraph.Link(3, 1, "E3"));
-
-
val objectLookup = new HashMap<ObjectGraph.Object, Integer>();
objGraph.objects().forEach(obj->{
val counter = objectLookup.size();
- d3jsGraph.nodes.add(new D3jsGraph.Node(counter, obj.name(),
obj.packageName(), obj.packageName()));
+ d3jsGraph.nodes.add(new D3jsGraph.Node(counter,
+ obj.name(),
+ obj.packageName(), // group
+ String.format("%s.%s", obj.packageName(), obj.name()) //
tooltip
+ ));
objectLookup.put(obj, counter);
});
@@ -117,7 +111,9 @@ public class ObjectGraphRendererD3js implements
ObjectGraph.Renderer {
protected void renderSvg(final StringBuilder sb, final D3jsGraph
d3jsGraph) {
- val noteText = "Note: Dragging nodes leaves them sticky.
Double-clicking releases them.";
+ val noteText = "Note: Dragging nodes leaves them sticky. "
+ + "Double-click releases them. "
+ + "Single-click toggles node highligh.";
sb.append("<div class=\"svg-container\">\n");
sb.append("<svg xmlns=\"http://www.w3.org/2000/svg\"
class=\"force-directed-graph\">\n");
@@ -163,6 +159,4 @@ public class ObjectGraphRendererD3js implements
ObjectGraph.Renderer {
.collect(Collectors.joining("\n"));
}
-}
-
-
+}
\ No newline at end of file
diff --git
a/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/d3js/assets/force-directed-graph.css
b/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/d3js/assets/force-directed-graph.css
index 372aed7d30..33ecb09c97 100644
---
a/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/d3js/assets/force-directed-graph.css
+++
b/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/d3js/assets/force-directed-graph.css
@@ -19,7 +19,6 @@
.links line {
stroke: #999;
- stroke-opacity: 0.6;
}
.nodes circle {
diff --git
a/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/d3js/assets/force-directed-graph.js
b/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/d3js/assets/force-directed-graph.js
index 88e236c7ce..e434a7c817 100644
---
a/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/d3js/assets/force-directed-graph.js
+++
b/valuetypes/asciidoc/builder/src/main/java/org/apache/causeway/valuetypes/asciidoc/builder/objgraph/d3js/assets/force-directed-graph.js
@@ -53,37 +53,33 @@ var frameBox = {
return this.height();
},
getViewBoxLiteral: function () {
- return ' ' + (this.minX()-this.border_padding) + ' ' +
(this.minY()-this.border_padding) + ' ' + (this.maxX()+this.border_padding) + '
' + (this.maxY()+this.border_padding);
+ return ' ' + (this.minX()-this.border_padding)
+ + ' ' + (this.minY()-this.border_padding)
+ + ' ' + (this.maxX()+this.border_padding)
+ + ' ' + (this.maxY()+this.border_padding);
},
- forceCenterBounded: function(x, y) {
+ forceBoxBounded: function(x, y) {
var nodes;
if (x == null) x = this.width() / 2;
if (y == null) y = this.height() / 2;
- var minX=this.minX()+this.node_radius,
- minY=this.minY()+this.node_radius,
- maxX=this.maxX()-this.node_radius,
- maxY=this.maxY()-this.node_radius;
+ const minX=this.minX()+this.node_radius,
+ minY=this.minY()+this.node_radius,
+ maxX=this.maxX()-this.node_radius,
+ maxY=this.maxY()-this.node_radius;
function force() {
var i,
n = nodes.length,
- node,
- sx = 0,
- sy = 0;
+ node;
for (i = 0; i < n; ++i) {
- node = nodes[i], sx += node.x, sy +=
node.y;
- }
-
- for (sx = sx / n - x, sy = sy / n - y, i = 0; i
< n; ++i) {
- node = nodes[i], node.x -= sx, node.y
-= sy;
- // don't let nodes leave the bounding
frame
- if(node.x>maxX){ node.x=maxX; }
- if(node.y>maxY){ node.y=maxY; }
- if(node.x<minX){ node.x=minX; }
- if(node.y<minY){ node.y=minY; }
+ node = nodes[i];
+ if(node.x>maxX){ node.x=maxX; }
+ if(node.y>maxY){ node.y=maxY; }
+ if(node.x<minX){ node.x=minX; }
+ if(node.y<minY){ node.y=minY; }
}
}
@@ -120,7 +116,34 @@ var rmodel = {
nodelabels : null,
edgelabels : null,
edgelines : null,
- rerenderFunction : null
+ rerenderFunction : null,
+ highlightedNodeIds : new Set(),
+ honorHighlightedNodes: function() {
+ let edgelines = this.edgelines;
+ let edgelabels = this.edgelabels;
+ let highlightedNodeIds = this.highlightedNodeIds;
+
+ // if nothing is highlighted, render default opacities
+ if(highlightedNodeIds.size == 0) {
+ if(edgelines) {
edgelines.attr("stroke-opacity", 0.6); }
+ if(edgelabels) { edgelabels.attr("opacity", 1);
}
+ return;
+ }
+
+ // update edge lines' opacity
+ if(edgelines){
+ edgelines.attr("stroke-opacity", function(d) {
+ //console.log("edgeTo " + d.target.id +
"->" + highlightedNodeIds.has(d.target.id));
+ return
highlightedNodeIds.has(d.target.id) ? ".6" : ".1";
+ });
+ }
+ // update edge labels' opacity
+ if(edgelabels){
+ edgelabels.attr("opacity", function(d) {
+ return
highlightedNodeIds.has(d.target.id) ? "1" : ".2";
+ });
+ }
+ }
};
function renderForceDirectedGraph(data, noteText) {
@@ -132,15 +155,21 @@ function renderForceDirectedGraph(data, noteText) {
var color = d3.scaleOrdinal(d3.schemeCategory20);
{ // options
-
- var checkBox1 = new svgCheckBox("Node
Labels").x(5).y(5).rx(5).ry(5).checked(ropts.enable_nodelabels);
- var checkBox2 = new svgCheckBox("Edge
Labels").x(5).y(5+24).rx(5).ry(5).checked(ropts.enable_edgelabels);
- var checkBox3 = new svgCheckBox("Edge
Arrows").x(5).y(5+24*2).rx(5).ry(5).checked(ropts.enable_edgearrows);
-
+
+ let inset = 5;
+ let textHeight = 24;
+
svg.append("text")
- .attr("x", 5)
- .attr("y", 5+24*3.6)
+ .attr("x", inset)
+ .attr("y", inset+textHeight*0.5)
.text(noteText);
+
+ var checkBox1 = new svgCheckBox("Node
Labels").x(inset).y(inset+textHeight*1).rx(5).ry(5)
+ .checked(ropts.enable_nodelabels);
+ var checkBox2 = new svgCheckBox("Edge
Labels").x(inset).y(inset+textHeight*2).rx(5).ry(5)
+ .checked(ropts.enable_edgelabels);
+ var checkBox3 = new svgCheckBox("Edge
Arrows").x(inset).y(inset+textHeight*3).rx(5).ry(5)
+ .checked(ropts.enable_edgearrows);
var updateCB = function () {
@@ -202,9 +231,12 @@ function renderForceDirectedGraph(data, noteText) {
}
var simulation = d3.forceSimulation()
- .force("link", d3.forceLink().id(function(d) { return d.id; }))
- .force("charge", d3.forceManyBody())
- .force("center", frameBox.forceCenterBounded());
+ .force("link", d3.forceLink().id((d) => d.id ))
+ //.force("charge", d3.forceManyBody().strength(-100))
+ .force("collide", d3.forceCollide((d) => 25))
+ .force("center", d3.forceCenter(frameBox.width()/2,
frameBox.height()/2))
+ .force("boxBounded", frameBox.forceBoxBounded())
+ ;
function renderGraph(graph) {
@@ -265,6 +297,7 @@ function renderForceDirectedGraph(data, noteText) {
.on("drag", dragged)
.on("end", dragended))
.on("dblclick", releaseNode)
+ .on("click", toggleNodeHighlight)
;
node.append("title")
@@ -295,7 +328,7 @@ function renderForceDirectedGraph(data, noteText) {
.style("pointer-events", "none")
.style("text-anchor", "middle")
.attr("class", "edgelabel")
- .text(function(d,i){return d.label;});
+ .text(function(d){return d.label;});
}
// getters
@@ -313,6 +346,7 @@ function renderForceDirectedGraph(data, noteText) {
return rmodel.edgelines;
}
rmodel.edgelines = createEdges();
+ rmodel.honorHighlightedNodes();
return rmodel.edgelines;
}
@@ -335,6 +369,7 @@ function renderForceDirectedGraph(data, noteText) {
return rmodel.edgelabels;
}
rmodel.edgelabels = createEdgeLabels();
+ rmodel.honorHighlightedNodes();
return rmodel.edgelabels;
}
@@ -372,7 +407,7 @@ function renderForceDirectedGraph(data, noteText) {
getEdgeLabels()
.attr("x", function(d) { return (d.source.x +
d.target.x)*0.5; })
.attr("y", function(d) { return (d.source.y +
d.target.y)*0.5; })
- .attr('transform', function(d,i){
+ .attr('transform', function(d){
var cx = (d.source.x + d.target.x)*0.5;
var cy = (d.source.y + d.target.y)*0.5;
var dx = d.target.x - d.source.x;
@@ -383,8 +418,6 @@ function renderForceDirectedGraph(data, noteText) {
return 'rotate('+degree+' '+cx+'
'+cy+')';
})
;
-
-
}
rmodel.rerenderFunction = ticked;
@@ -416,5 +449,16 @@ function renderForceDirectedGraph(data, noteText) {
d.fy = null;
}
+ function toggleNodeHighlight(node) {
+ let nodeId = node.id;
+ if(rmodel.highlightedNodeIds.has(nodeId)) {
+ rmodel.highlightedNodeIds.delete(nodeId);
+ //console.log("del " + nodeId);
+ } else {
+ rmodel.highlightedNodeIds.add(nodeId);
+ //console.log("add " + nodeId);
+ }
+ rmodel.honorHighlightedNodes();
+ }
}
\ No newline at end of file