@@ -390,16 +390,23 @@ public String printHead() {
390390 // google chart import
391391 + "<script type=\" text/javascript\" src=\" https://www.gstatic.com/charts/loader.js\" ></script>\n "
392392 // d3 dot graph imports
393- + "<script src=\" https://d3js.org/d3.v5.min.js\" ></script>\n "
394- + "<script src=\" https://cdnjs.cloudflare.com/ajax/libs/d3-graphviz/3.0.5/d3-graphviz.min.js\" ></script>\n "
395- +
"<script src=\" https://unpkg.com/@hpcc-js/[email protected] /dist/index.min.js\" ></script>\n " 393+ // + "<script src=\"https://d3js.org/d3.v5.min.js\"></script>\n"
394+ // + "<script
395+ // src=\"https://cdnjs.cloudflare.com/ajax/libs/d3-graphviz/3.0.5/d3-graphviz.min.js\"></script>\n"
396+ // + "<script
397+ // src=\"https://unpkg.com/@hpcc-js/[email protected] /dist/index.min.js\"></script>\n" 398+
399+ // + "<script
400+ // src=\"https://cdn.jsdelivr.net/npm/@hpcc-js/[email protected] /dist/index.min.js\"></script>\n" 401+ +
"<script src=\" https://cdn.jsdelivr.net/npm/[email protected] /dist/svg-pan-zoom.min.js\" ></script>" 402+
396403 // sigma graph imports - sigma, graphology, graphlib, and graphlib-dot
397404 + "<script src=\" https://cdnjs.cloudflare.com/ajax/libs/sigma.js/2.4.0/sigma.min.js\" ></script>\n "
398405 + "<script src=\" https://cdnjs.cloudflare.com/ajax/libs/graphology/0.25.4/graphology.umd.min.js\" ></script>\n "
399406 // may only need graphlib-dot
400407 + "<script src=\" https://cdnjs.cloudflare.com/ajax/libs/graphlib/2.1.8/graphlib.min.js\" ></script>\n "
401408 +
"<script src=\" https://cdn.jsdelivr.net/npm/[email protected] /dist/graphlib-dot.min.js\" ></script>\n " 402- + "<script src=\" https://unpkg.com /3d-force-graph\" ></script>\n " ;
409+ + "<script src=\" https://cdn.jsdelivr.net/npm /3d-force-graph\" ></script>\n " ;
403410 }
404411
405412 String printScripts () {
@@ -489,86 +496,88 @@ String renderCBOChart(List<RankedDisharmony> rankedCBODisharmonies, int maxCboPr
489496 }
490497
491498 @ Override
492- public String renderClassGraphDotImage () {
499+ public String renderClassGraphVisuals () {
493500 String dot = buildClassGraphDot (classGraph );
494-
495501 String classGraphName = "classGraph" ;
496502
497503 StringBuilder stringBuilder = new StringBuilder ();
498- stringBuilder .append ("<h1 align=\" center\" >Class Map</h1>" );
504+ stringBuilder .append (generateGraphButtons (classGraphName , dot ));
505+
499506 stringBuilder .append (
500507 "<div align=\" center\" >Excludes classes that have no incoming and outgoing edges<br></div>" );
508+
509+ int classCount = classGraph .vertexSet ().size ();
510+ int relationshipCount = classGraph .edgeSet ().size ();
511+ stringBuilder .append ("<div align=\" center\" >Number of classes: " + classCount + " Number of relationships: "
512+ + relationshipCount + "<br></div>" );
513+ if (classCount + relationshipCount < d3Threshold ) {
514+ stringBuilder .append (generateDotImage (classGraphName ));
515+ } else {
516+ // revisit and add DOT SVG popup button
517+ stringBuilder .append ("<div align=\" center\" >\n SVG is too big to render quickly</div>\n " );
518+ }
519+
520+ return stringBuilder .toString ();
521+ }
522+
523+ private StringBuilder generateGraphButtons (String graphName , String dot ) {
524+ StringBuilder stringBuilder = new StringBuilder ();
525+ stringBuilder .append ("<h1 align=\" center\" >Class Map</h1>" );
501526 stringBuilder .append ("<script>\n " );
502- stringBuilder .append ("const " + classGraphName + "_dot = " + dot + "\n " );
527+ stringBuilder .append ("const " + graphName + "_dot = " + dot + "\n " );
503528 stringBuilder .append ("</script>\n " );
504- stringBuilder .append (generateForce3DPopup (classGraphName ));
505- stringBuilder .append (generate2DPopup (classGraphName ));
506- stringBuilder .append (generateHidePopup (classGraphName ));
529+ stringBuilder .append (generateForce3DPopup (graphName ));
530+ stringBuilder .append (generate2DPopup (graphName ));
531+ stringBuilder .append (generateHidePopup (graphName ));
507532
508533 stringBuilder .append ("<div align=\" center\" >\n Red lines represent back edges to remove.<br>\n " );
509534 stringBuilder .append ("Zoom in / out with your mouse wheel and click/move to drag the image.\n " );
510535 stringBuilder .append ("</div>\n " );
536+ return stringBuilder ;
537+ }
511538
539+ private static String generateDotImage (String graphName ) {
512540 // revisit and add D3 popup button as well
513- if (classGraph .vertexSet ().size () + classGraph .edgeSet ().size () < d3Threshold ) {
514- stringBuilder .append (
515- "<div align=\" center\" id=\" " + classGraphName + "\" style=\" border: thin solid black\" ></div>\n " );
516- stringBuilder .append ("<script>\n " );
517- stringBuilder .append ("d3.select(\" #" + classGraphName + "\" )\n " );
518- stringBuilder .append (".graphviz()\n " );
519- stringBuilder .append (".width(screen.width - " + pixels + ")\n " );
520- stringBuilder .append (".height(screen.height)\n " );
521- stringBuilder .append (".fit(true)\n " );
522- stringBuilder .append (".renderDot(" + classGraphName + "_dot);\n " );
523- stringBuilder .append ("</script>\n " );
524- } else {
525- // revisit and add D3 SVG popup button
526- stringBuilder .append ("<div align=\" center\" >\n Class Map SVG is too big to render SVG quickly</div>\n " );
527- }
528-
529- return stringBuilder .toString ();
541+ return "<div id=\" " + graphName
542+ + "\" style=\" width: 95%; margin: auto; border: thin solid black\" ></div>\n "
543+ + "<script type=\" module\" >\n "
544+ + "import { Graphviz } from \" https://cdn.jsdelivr.net/npm/@hpcc-js/wasm/dist/index.js\" ;\n "
545+ + " if (Graphviz) {\n "
546+ + " const graphviz = await Graphviz.load();\n "
547+ + " let svg = graphviz.layout("
548+ + graphName + "_dot, \" svg\" , \" dot\" );\n "
549+ + " // Set desired width and height\n "
550+ + "\n "
551+ + " // Modify the SVG string to include width and height attributes\n "
552+ + " svg = svg.replace('<svg ', `<svg width=\" screen.width\" height=\" screen.height\" `);\n "
553+ + "\n "
554+ + " document.getElementById(\" "
555+ + graphName + "\" ).innerHTML = svg;\n " + "\n "
556+ + " // Make the SVG zoomable\n "
557+ + " svgPanZoom('#"
558+ + graphName + " svg', {\n " + " zoomEnabled: true,\n "
559+ + " controlIconsEnabled: true\n "
560+ + " });\n "
561+ + " }\n " + "</script>\n " ;
530562 }
531563
532564 String buildClassGraphDot (Graph <String , DefaultWeightedEdge > classGraph ) {
533565 StringBuilder dot = new StringBuilder ();
534566 dot .append ("`strict digraph G {\n " );
535567
536- Set <String > vertexesToRender = new HashSet <>();
537568 for (DefaultWeightedEdge edge : classGraph .edgeSet ()) {
538- // DownloadManager -> Download [ label="1" color="red" ];
569+ renderEdge (classGraph , edge , dot );
570+ }
539571
540- // render edge
572+ // capture only classes that have a relationship with one or more other classes
573+ Set <String > vertexesToRender = new HashSet <>();
574+ for (DefaultWeightedEdge edge : classGraph .edgeSet ()) {
541575 String [] vertexes = extractVertexes (edge );
542- String start = getClassName (vertexes [0 ].trim ()).replace ("$" , "_" );
543- String end = getClassName (vertexes [1 ].trim ()).replace ("$" , "_" );
544-
545576 vertexesToRender .add (vertexes [0 ].trim ());
546577 vertexesToRender .add (vertexes [1 ].trim ());
547-
548- dot .append (start );
549- dot .append (" -> " );
550- dot .append (end );
551-
552- // render edge attributes
553- int edgeWeight = (int ) classGraph .getEdgeWeight (edge );
554- dot .append (" [ " );
555- dot .append ("label = \" " );
556- dot .append (edgeWeight );
557- dot .append ("\" " );
558- dot .append ("weight = \" " );
559- dot .append (edgeWeight );
560- dot .append ("\" " );
561-
562- if (edgesAboveDiagonal .contains (edge )) {
563- dot .append (" color = \" red\" " );
564- }
565-
566- dot .append (" ];\n " );
567578 }
568579
569580 // render vertices
570- // e.g DownloadManager;
571- // for (String vertex : classGraph.vertexSet()) {
572581 for (String vertex : vertexesToRender ) {
573582 dot .append (getClassName (vertex ).replace ("$" , "_" ));
574583 dot .append (";\n " );
@@ -578,40 +587,49 @@ String buildClassGraphDot(Graph<String, DefaultWeightedEdge> classGraph) {
578587 return dot .toString ();
579588 }
580589
590+ private void renderEdge (
591+ Graph <String , DefaultWeightedEdge > classGraph , DefaultWeightedEdge edge , StringBuilder dot ) {
592+ // render edge
593+ String [] vertexes = extractVertexes (edge );
594+
595+ String start = getClassName (vertexes [0 ].trim ()).replace ("$" , "_" );
596+ String end = getClassName (vertexes [1 ].trim ()).replace ("$" , "_" );
597+
598+ dot .append (start );
599+ dot .append (" -> " );
600+ dot .append (end );
601+
602+ // render edge attributes
603+ int edgeWeight = (int ) classGraph .getEdgeWeight (edge );
604+ dot .append (" [ " );
605+ dot .append ("label = \" " );
606+ dot .append (edgeWeight );
607+ dot .append ("\" " );
608+ dot .append ("weight = \" " );
609+ dot .append (edgeWeight );
610+ dot .append ("\" " );
611+
612+ if (edgesAboveDiagonal .contains (edge )) {
613+ dot .append (" color = \" red\" " );
614+ }
615+
616+ dot .append (" ];\n " );
617+ }
618+
581619 @ Override
582- public String renderCycleDotImage (RankedCycle cycle ) {
620+ public String renderCycleVisuals (RankedCycle cycle ) {
583621 String dot = buildCycleDot (classGraph , cycle );
584622
585623 String cycleName = getClassName (cycle .getCycleName ()).replace ("$" , "_" );
586624
587625 StringBuilder stringBuilder = new StringBuilder ();
588- stringBuilder .append ("<script>\n " );
589- stringBuilder .append ("const " + cycleName + "_dot = " + dot + "\n " );
590- stringBuilder .append ("</script>\n " );
591- stringBuilder .append (generateForce3DPopup (cycleName ));
592- stringBuilder .append (generate2DPopup (cycleName ));
593- stringBuilder .append (generateHidePopup (cycleName ));
594-
595- stringBuilder .append ("<div align=\" center\" >\n " );
596- stringBuilder .append ("Red lines represent back edges to remove.<br>\n " );
597- stringBuilder .append ("Zoom in / out with your mouse wheel and click/move to drag the image.\n " );
598- stringBuilder .append ("</div>\n " );
626+ stringBuilder .append (generateGraphButtons (cycleName , dot ));
599627
600628 if (cycle .getCycleNodes ().size () + cycle .getEdgeSet ().size () < d3Threshold ) {
601- stringBuilder .append (
602- "<div align=\" center\" id=\" " + cycleName + "\" style=\" border: thin solid black\" ></div>\n " );
603- stringBuilder .append ("<script>\n " );
604- stringBuilder .append ("d3.select(\" #" + cycleName + "\" )\n " );
605- stringBuilder .append (".graphviz()\n " );
606- stringBuilder .append (".width(screen.width - " + pixels + ")\n " );
607- stringBuilder .append (".height(screen.height)\n " );
608- stringBuilder .append (".fit(true)\n " );
609- stringBuilder .append (".renderDot(" + cycleName + "_dot);\n " );
610- stringBuilder .append ("</script>\n " );
629+ stringBuilder .append (generateDotImage (cycleName ));
611630 } else {
612- // revisit and add D3 SVG popup button
613- stringBuilder .append (
614- "<div align=\" center\" >\n Cycle " + cycleName + " SVG is too big to render SVG quickly</div>\n " );
631+ // revisit and add DOT SVG popup button
632+ stringBuilder .append ("<div align=\" center\" >\n SVG is too big to render quickly</div>\n " );
615633 }
616634
617635 stringBuilder .append ("<br/>\n " );
@@ -622,49 +640,18 @@ public String renderCycleDotImage(RankedCycle cycle) {
622640
623641 String buildCycleDot (Graph <String , DefaultWeightedEdge > classGraph , RankedCycle cycle ) {
624642 StringBuilder dot = new StringBuilder ();
625-
626643 dot .append ("`strict digraph G {\n " );
627644
645+ for (DefaultWeightedEdge edge : cycle .getEdgeSet ()) {
646+ renderEdge (classGraph , edge , dot );
647+ }
648+
628649 // render vertices
629- // e.g DownloadManager;
630650 for (String vertex : cycle .getVertexSet ()) {
631651 dot .append (getClassName (vertex ).replace ("$" , "_" ));
632652 dot .append (";\n " );
633653 }
634654
635- for (DefaultWeightedEdge edge : cycle .getEdgeSet ()) {
636- // DownloadManager -> Download [ label="1" color="red" ];
637-
638- // render edge
639- String [] vertexes = extractVertexes (edge );
640- String start = getClassName (vertexes [0 ].trim ()).replace ("$" , "_" );
641- String end = getClassName (vertexes [1 ].trim ()).replace ("$" , "_" );
642-
643- dot .append (start );
644- dot .append (" -> " );
645- dot .append (end );
646-
647- // render edge attributes
648- int edgeWeight = (int ) classGraph .getEdgeWeight (edge );
649- dot .append (" [ " );
650- dot .append ("label = \" " );
651- dot .append (edgeWeight );
652- dot .append ("\" " );
653- dot .append ("weight = \" " );
654- dot .append (edgeWeight );
655- dot .append ("\" " );
656-
657- if (edgesAboveDiagonal .contains (edge )) {
658- dot .append (" color = \" red\" " );
659- }
660-
661- if (cycle .getMinCutEdges ().contains (edge ) && !edgesAboveDiagonal .contains (edge )) {
662- dot .append (" color = \" blue\" " );
663- }
664-
665- dot .append (" ];\n " );
666- }
667-
668655 dot .append ("}`;" );
669656
670657 return dot .toString ().replace ("$" , "_" );
0 commit comments