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

Reply via email to