Skip to content

Commit 9304631

Browse files
#101 Rendering DOT images in Maven report
Rendering DOT images in Maven report and touched up the layout in the HTML Report
1 parent 691e632 commit 9304631

File tree

3 files changed

+159
-37
lines changed

3 files changed

+159
-37
lines changed

refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstMavenReport.java

Lines changed: 146 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

report/src/main/java/org/hjug/refactorfirst/report/HtmlReport.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,22 @@ public void renderCycleImage(
178178
Graph<String, DefaultWeightedEdge> classGraph, RankedCycle cycle, StringBuilder stringBuilder) {
179179
String dot = buildDot(classGraph, cycle);
180180

181-
stringBuilder.append("<div align=\"center\" id=\"" + cycle.getCycleName() + "\"></div>\n");
181+
stringBuilder.append("<div align=\"center\" id=\"" + cycle.getCycleName()
182+
+ "\" style=\"border: thin solid black\"></div>\n");
182183
stringBuilder.append("<script>\n");
183184
stringBuilder.append("d3.select(\"#" + cycle.getCycleName() + "\")\n");
184185
stringBuilder.append(".graphviz()\n");
186+
stringBuilder.append(".width(screen.width - 200)\n");
187+
stringBuilder.append(".height(screen.height)\n");
188+
stringBuilder.append(".fit(true)\n");
185189
stringBuilder.append(".renderDot(" + dot + ");\n");
186190
stringBuilder.append("</script>\n");
191+
192+
stringBuilder.append("<div align=\"center\">");
193+
stringBuilder.append("<p>Red arrows represent relationship(s) to remove to decompose cycle</p>");
194+
stringBuilder.append("</div>");
195+
stringBuilder.append("<br/>");
196+
stringBuilder.append("<br/>");
187197
}
188198

189199
String buildDot(Graph<String, DefaultWeightedEdge> classGraph, RankedCycle cycle) {

report/src/main/java/org/hjug/refactorfirst/report/SimpleHtmlReport.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ private void renderCycles(
232232

233233
stringBuilder.append(
234234
"<h2 align=\"center\">Class Cycles by the numbers: (Refactor starting with Priority 1)</h2>\n");
235+
stringBuilder.append(
236+
"<p align=\"center\">Note: often only one minimum cut relationship needs to be removed</p>");
235237
stringBuilder.append("<table align=\"center\" border=\"5px\">\n");
236238

237239
String[] cycleTableHeadings;

0 commit comments

Comments
 (0)