@@ -68,10 +68,6 @@ public String getDescription(Locale locale) {
6868 + " have the highest priority values." ;
6969 }
7070
71- public final String [] cycleTableHeadings = {
72- "Cycle Name" , "Priority" , "Change Proneness Rank" , "Class Count" , "Relationship Count" , "Minimum Cuts"
73- };
74-
7571 public final String [] classCycleTableHeadings = {"Classes" , "Relationships" };
7672
7773 private Graph <String , DefaultWeightedEdge > classGraph ;
@@ -164,6 +160,28 @@ public void executeReport(Locale locale) throws MavenReportException {
164160
165161 mainSink .unknown (script , new Object [] {HtmlMarkup .TAG_TYPE_START }, cboJavascript );
166162 mainSink .unknown (script , new Object [] {HtmlMarkup .TAG_TYPE_END }, null );
163+
164+ SinkEventAttributeSet d3js = new SinkEventAttributeSet ();
165+ d3js .addAttribute (SinkEventAttributes .TYPE , "text/javascript" );
166+ d3js .addAttribute (SinkEventAttributes .SRC , "https://d3js.org/d3.v5.min.js" );
167+
168+ mainSink .unknown (script , new Object [] {HtmlMarkup .TAG_TYPE_START }, d3js );
169+ mainSink .unknown (script , new Object [] {HtmlMarkup .TAG_TYPE_END }, null );
170+
171+ SinkEventAttributeSet graphViz = new SinkEventAttributeSet ();
172+ graphViz .addAttribute (SinkEventAttributes .TYPE , "text/javascript" );
173+ graphViz .
addAttribute (
SinkEventAttributes .
SRC ,
"https://unpkg.com/[email protected] /build/d3-graphviz.min.js" );
174+
175+ mainSink .unknown (script , new Object [] {HtmlMarkup .TAG_TYPE_START }, graphViz );
176+ mainSink .unknown (script , new Object [] {HtmlMarkup .TAG_TYPE_END }, null );
177+
178+ SinkEventAttributeSet wasm = new SinkEventAttributeSet ();
179+ wasm .addAttribute (SinkEventAttributes .TYPE , "text/javascript" );
180+ wasm .
addAttribute (
SinkEventAttributes .
SRC ,
"https://unpkg.com/@hpcc-js/[email protected] /dist/index.min.js" );
181+
182+ mainSink .unknown (script , new Object [] {HtmlMarkup .TAG_TYPE_START }, wasm );
183+ mainSink .unknown (script , new Object [] {HtmlMarkup .TAG_TYPE_END }, null );
184+
167185 mainSink .head_ ();
168186
169187 mainSink .body ();
@@ -472,12 +490,26 @@ private void renderCycles(
472490 mainSink .section2_ ();
473491 mainSink .division_ ();
474492
493+ mainSink .paragraph (alignCenter );
494+ mainSink .text ("Note: often only one minimum cut relationship needs to be removed" );
495+ mainSink .paragraph_ ();
496+
475497 mainSink .table ();
476498 mainSink .tableRows (new int [] {Sink .JUSTIFY_LEFT }, true );
477499
478500 // Content
479501 // header row
480502
503+ String [] cycleTableHeadings ;
504+ if (showDetails ) {
505+ cycleTableHeadings = new String [] {
506+ "Cycle Name" , "Priority" , "Change Proneness Rank" , "Class Count" , "Relationship Count" , "Minimum Cuts"
507+ };
508+ } else {
509+ cycleTableHeadings =
510+ new String [] {"Cycle Name" , "Priority" , "Class Count" , "Relationship Count" , "Minimum Cuts" };
511+ }
512+
481513 mainSink .tableRow ();
482514 for (String heading : cycleTableHeadings ) {
483515 drawTableHeaderCell (heading , mainSink );
@@ -492,15 +524,27 @@ private void renderCycles(
492524 edgesToCut .append (minCutEdge + ":" + (int ) classGraph .getEdgeWeight (minCutEdge ));
493525 }
494526
495- // "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Min Cuts"
496- String [] rankedCycleData = {
497- rankedCycle .getCycleName (),
498- rankedCycle .getPriority ().toString (),
499- rankedCycle .getChangePronenessRank ().toString (),
500- String .valueOf (rankedCycle .getCycleNodes ().size ()),
501- String .valueOf (rankedCycle .getEdgeSet ().size ()),
502- edgesToCut .toString ()
503- };
527+ String [] rankedCycleData ;
528+ if (showDetails ) {
529+ // "Cycle Name", "Priority", "Change Proneness Rank", "Class Count", "Relationship Count", "Min Cuts"
530+ rankedCycleData = new String [] {
531+ rankedCycle .getCycleName (),
532+ rankedCycle .getPriority ().toString (),
533+ rankedCycle .getChangePronenessRank ().toString (),
534+ String .valueOf (rankedCycle .getCycleNodes ().size ()),
535+ String .valueOf (rankedCycle .getEdgeSet ().size ()),
536+ edgesToCut .toString ()
537+ };
538+ } else {
539+ // "Cycle Name", "Priority", "Class Count", "Relationship Count", "Min Cuts"
540+ rankedCycleData = new String [] {
541+ rankedCycle .getCycleName (),
542+ rankedCycle .getPriority ().toString (),
543+ String .valueOf (rankedCycle .getCycleNodes ().size ()),
544+ String .valueOf (rankedCycle .getEdgeSet ().size ()),
545+ edgesToCut .toString ()
546+ };
547+ }
504548
505549 for (String rowData : rankedCycleData ) {
506550 drawCycleTableCell (rowData , mainSink );
@@ -513,12 +557,11 @@ private void renderCycles(
513557 mainSink .table_ ();
514558
515559 for (RankedCycle rankedCycle : rankedCycles ) {
516- renderCycleTable (outputDirectory , mainSink , rankedCycle , formatter );
560+ renderCycle (outputDirectory , mainSink , rankedCycle , formatter );
517561 }
518562 }
519563
520- private void renderCycleTable (
521- String outputDirectory , Sink mainSink , RankedCycle cycle , DateTimeFormatter formatter ) {
564+ private void renderCycle (String outputDirectory , Sink mainSink , RankedCycle cycle , DateTimeFormatter formatter ) {
522565
523566 mainSink .lineBreak ();
524567 mainSink .lineBreak ();
@@ -537,7 +580,7 @@ private void renderCycleTable(
537580 mainSink .section2_ ();
538581 mainSink .division_ ();
539582
540- renderCycleImage (cycle . getCycleName (), mainSink , outputDirectory );
583+ renderCycleImage (classGraph , cycle , mainSink );
541584
542585 mainSink .division (alignCenter );
543586 mainSink .bold ();
@@ -580,25 +623,6 @@ private void renderCycleTable(
580623 mainSink .table_ ();
581624 }
582625
583- public void renderCycleImage (String cycleName , Sink mainSink , String outputDirectory ) {
584- SinkEventAttributeSet alignCenter = new SinkEventAttributeSet ();
585- alignCenter .addAttribute (SinkEventAttributes .ALIGN , "center" );
586- mainSink .division (alignCenter );
587-
588- SinkEventAttributeSet imageAttributes = new SinkEventAttributeSet ();
589- imageAttributes .addAttribute (SinkEventAttributes .TYPE , "img" );
590- imageAttributes .addAttribute (SinkEventAttributes .SRC , "./refactorFirst/cycles/graph" + cycleName + ".png" );
591- imageAttributes .addAttribute (SinkEventAttributes .WIDTH , 1000 );
592- imageAttributes .addAttribute (SinkEventAttributes .HEIGHT , 1000 );
593- imageAttributes .addAttribute (SinkEventAttributes .ALT , "Cycle " + cycleName );
594-
595- mainSink .unknown ("img" , new Object [] {HtmlMarkup .TAG_TYPE_SIMPLE }, imageAttributes );
596-
597- mainSink .division_ ();
598- mainSink .lineBreak ();
599- mainSink .lineBreak ();
600- }
601-
602626 private void renderLegend (Sink mainSink , String legendHeading , String xAxis ) {
603627 SinkEventAttributeSet width = new SinkEventAttributeSet ();
604628 width .addAttribute (SinkEventAttributes .STYLE , "width:350px" );
@@ -809,4 +833,90 @@ void writeGCBOGchartJs(List<RankedDisharmony> rankedDisharmonies, int maxPriorit
809833 log .error ("Error writing CBO chart script file" , e );
810834 }
811835 }
836+
837+ void renderCycleImage (Graph <String , DefaultWeightedEdge > classGraph , RankedCycle cycle , Sink mainSink ) {
838+
839+ SinkEventAttributeSet graphDivAttrs = new SinkEventAttributeSet ();
840+ graphDivAttrs .addAttribute (SinkEventAttributes .ALIGN , "center" );
841+ graphDivAttrs .addAttribute (SinkEventAttributes .ID , cycle .getCycleName ());
842+ graphDivAttrs .addAttribute (SinkEventAttributes .STYLE , "border: thin solid black" );
843+
844+ mainSink .division (graphDivAttrs );
845+ mainSink .division_ ();
846+
847+ String dot = buildDot (classGraph , cycle );
848+
849+ StringBuilder d3chart = new StringBuilder ();
850+ d3chart .append ("d3.select(\" #" + cycle .getCycleName () + "\" )\n " );
851+ d3chart .append (".graphviz()\n " );
852+ d3chart .append (".width(screen.width - 300)\n " );
853+ d3chart .append (".height(screen.height)\n " );
854+ d3chart .append (".fit(true)\n " );
855+ d3chart .append (".renderDot(" + dot + ");\n " );
856+
857+ SinkEventAttributeSet dotChartScript = new SinkEventAttributeSet ();
858+ dotChartScript .addAttribute (SinkEventAttributes .TYPE , "text/javascript" );
859+
860+ String script = "script" ;
861+ mainSink .unknown (script , new Object [] {HtmlMarkup .TAG_TYPE_START }, dotChartScript );
862+
863+ mainSink .rawText (d3chart .toString ());
864+ mainSink .unknown (script , new Object [] {HtmlMarkup .TAG_TYPE_END }, null );
865+
866+ SinkEventAttributeSet alignCenter = new SinkEventAttributeSet ();
867+ alignCenter .addAttribute (SinkEventAttributes .ALIGN , "center" );
868+
869+ mainSink .paragraph (alignCenter );
870+ mainSink .text ("Red arrows represent relationship(s) to remove to decompose cycle" );
871+ mainSink .paragraph_ ();
872+
873+ mainSink .lineBreak ();
874+ mainSink .lineBreak ();
875+ }
876+
877+ String buildDot (Graph <String , DefaultWeightedEdge > classGraph , RankedCycle cycle ) {
878+ StringBuilder dot = new StringBuilder ();
879+
880+ dot .append ("'strict digraph G {\\ n' +\n " );
881+
882+ // render vertices
883+ // e.g DownloadManager;
884+ for (String vertex : cycle .getVertexSet ()) {
885+ dot .append ("'" );
886+ dot .append (vertex );
887+ dot .append (";\\ n' +\n " );
888+ }
889+
890+ for (DefaultWeightedEdge edge : cycle .getEdgeSet ()) {
891+ // 'DownloadManager -> Download [ label="1" color="red" ];'
892+
893+ // render edge
894+ String [] vertexes =
895+ edge .toString ().replace ("(" , "" ).replace (")" , "" ).split (":" );
896+
897+ String start = vertexes [0 ].trim ();
898+ String end = vertexes [1 ].trim ();
899+
900+ dot .append ("'" );
901+ dot .append (start );
902+ dot .append (" -> " );
903+ dot .append (end );
904+
905+ // render edge attributes
906+ dot .append (" [ " );
907+ dot .append ("label = \" " );
908+ dot .append ((int ) classGraph .getEdgeWeight (edge ));
909+ dot .append ("\" " );
910+
911+ if (cycle .getMinCutEdges ().contains (edge )) {
912+ dot .append (" color = \" red\" " );
913+ }
914+
915+ dot .append (" ];\\ n' +\n " );
916+ }
917+
918+ dot .append ("'}'" );
919+
920+ return dot .toString ();
921+ }
812922}
0 commit comments