Skip to content

Commit 6809e3e

Browse files
committed
adding additional features to the bar charts
1 parent ec4837e commit 6809e3e

5 files changed

Lines changed: 258 additions & 36 deletions

File tree

pom.xml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<modelVersion>4.0.0</modelVersion>
55
<groupId>com.factionsecurity</groupId>
66
<artifactId>faction-vulnerability-bar-chart</artifactId>
7-
<version>1.0</version>
7+
<version>1.1</version>
88
<name>Faction Vulnerability Bar Chart</name>
99
<dependencies>
1010
<dependency>
@@ -17,6 +17,18 @@
1717
<artifactId>jfreechart</artifactId>
1818
<version>1.5.4</version>
1919
</dependency>
20+
<dependency>
21+
<groupId>org.junit.jupiter</groupId>
22+
<artifactId>junit-jupiter-api</artifactId>
23+
<version>5.9.1</version>
24+
<scope>test</scope>
25+
</dependency>
26+
<dependency>
27+
<groupId>org.junit.jupiter</groupId>
28+
<artifactId>junit-jupiter-engine</artifactId>
29+
<version>5.9.1</version>
30+
<scope>test</scope>
31+
</dependency>
2032
</dependencies>
2133
<build>
2234
<plugins>

src/main/java/com/faction/VulnerabilityBarChart.java

Lines changed: 204 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package com.faction;
22

33
import java.util.ArrayList;
4+
45
import java.util.Arrays;
6+
import java.util.Collections;
57
import java.util.HashMap;
68
import java.util.LinkedHashMap;
9+
import java.util.LinkedList;
710
import java.util.List;
811
import java.util.Map;
912
import java.util.stream.Collectors;
@@ -16,27 +19,54 @@
1619
import org.jfree.chart.ChartFactory;
1720
import org.jfree.chart.ChartUtils;
1821
import org.jfree.chart.JFreeChart;
22+
import org.jfree.chart.axis.CategoryAxis;
1923
import org.jfree.chart.axis.NumberAxis;
2024
import org.jfree.chart.axis.NumberTickUnit;
25+
import org.jfree.chart.axis.ValueAxis;
26+
import org.jfree.chart.labels.ItemLabelAnchor;
27+
import org.jfree.chart.labels.ItemLabelPosition;
28+
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
2129
import org.jfree.chart.plot.CategoryPlot;
2230
import org.jfree.chart.plot.PlotOrientation;
2331
import org.jfree.chart.renderer.category.BarRenderer;
2432
import org.jfree.chart.renderer.category.StandardBarPainter;
33+
import org.jfree.chart.ui.TextAnchor;
2534
import org.jfree.chart.renderer.category.CategoryItemRenderer;
35+
import org.jfree.chart.renderer.category.CategoryItemRendererState;
2636
import org.jfree.data.category.CategoryDataset;
2737
import org.jfree.data.category.DefaultCategoryDataset;
2838
import org.sonatype.plexus.components.cipher.Base64;
2939

40+
import java.awt.BasicStroke;
3041
import java.awt.Color;
3142
import java.awt.Font;
43+
import java.awt.FontMetrics;
44+
import java.awt.Graphics2D;
3245
import java.awt.Paint;
46+
import java.awt.geom.Rectangle2D;
3347
import java.io.ByteArrayOutputStream;
3448
import java.io.IOException;
3549

3650
public class VulnerabilityBarChart extends BaseExtension implements com.faction.extender.ReportManager {
3751

3852
private int totalLevels = 6;
3953
private LinkedHashMap<String,Color> severities = new LinkedHashMap<>();
54+
private HashMap<Long, SevObj> sevMap = new HashMap<>();
55+
private Boolean showNumbers = false;
56+
private Boolean showBorders = false;
57+
private String yAxisName="";
58+
59+
60+
private class SevObj {
61+
public Color color;
62+
public Color border;
63+
public String name;
64+
public SevObj(String name, Color color, Color border) {
65+
this.color = color;
66+
this.border = border;
67+
this.name = name;
68+
}
69+
}
4070

4171

4272
@Override
@@ -55,31 +85,70 @@ public String reportCreate(Assessment asmt, List<Vulnerability> vulns, String re
5585
* This section represents the getters from the user supplied configs
5686
*
5787
*/
88+
private Color getColorOrDefault(String value) {
89+
if(value == null) {
90+
return new Color(0x000000);
91+
}else {
92+
try {
93+
return new Color(Integer.parseInt(value.trim(), 16));
94+
}catch(Exception e) {
95+
return new Color(0x000000);
96+
}
97+
}
98+
}
5899
private void setUpSeverities() {
59100
String colorsCSV = this.getConfigs().get("Severity Colors");
101+
String sevMapCSV = this.getConfigs().get("Severity Mapping");
102+
String bordersCSV = this.getConfigs().get("Border Colors");
103+
showNumbers = this.getConfigs().get("Show Numbers") == null? false : Boolean.parseBoolean(this.getConfigs().get("Show Numbers").trim());
104+
showBorders = this.getConfigs().get("Show Borders") == null? false : Boolean.parseBoolean(this.getConfigs().get("Show Borders").trim());
105+
yAxisName = this.getConfigs().get("Y Axis Label") == null? "" : this.getConfigs().get("Y Axis Label");
60106
String w = this.getConfigs().get("Width");
61107
if(colorsCSV == null || colorsCSV.trim().equals("")) {
62-
severities.put("Critical", new Color(0xdd4b39));
63-
severities.put("High", new Color(0xf39c12));
64-
severities.put("Medium", new Color(0x00c0ef));
65-
severities.put("Low", new Color(0x39cccc));
66-
severities.put("Recommended", new Color(0x00a65a));
67-
severities.put("Informational", new Color(0x95a5a6));
108+
this.sevMap.put(5l, new SevObj("Critical", new Color(0xdd4b39), new Color(0xdd4b39)));
109+
this.sevMap.put(4l, new SevObj("High", new Color(0xf39c12), new Color(0xf39c12)));
110+
this.sevMap.put(3l, new SevObj("Medium", new Color(0x00c0ef), new Color(0x00c0ef)));
111+
this.sevMap.put(2l, new SevObj("Low", new Color(0x39cccc), new Color(0x39cccc)));
112+
this.sevMap.put(1l, new SevObj("Recommended", new Color(0x00a65a), new Color(0x00a65a) ));
113+
this.sevMap.put(0l, new SevObj("Informational", new Color(0x95a5a6), new Color(0x95a5a6)));
68114
}else {
69115
String [] colors = colorsCSV.split(",");
116+
String [] sevIds = sevMapCSV.split(",");
117+
String [] borderColors = bordersCSV.split(",");
70118
this.severities = Arrays
71119
.stream(colors)
72120
.map( color -> color.split(":"))
73121
.collect(Collectors.toMap(
74122
keyValue -> keyValue[0],
75-
keyValue -> new Color(Integer.parseInt(keyValue[1].trim(), 16)),
123+
keyValue -> this.getColorOrDefault(keyValue[1]),
124+
(v1,v2) -> v1,
125+
LinkedHashMap<String, Color>::new
126+
));
127+
LinkedHashMap<String,Color> borders = Arrays
128+
.stream(borderColors)
129+
.map( color -> color.split(":"))
130+
.collect(Collectors.toMap(
131+
keyValue -> keyValue[0],
132+
keyValue -> this.getColorOrDefault(keyValue[1]),
76133
(v1,v2) -> v1,
77134
LinkedHashMap<String, Color>::new
78135
));
79-
this.totalLevels = colors.length;
80136

81-
String [] sevs = this.severities.keySet().toArray(new String[0]);
137+
this.sevMap = Arrays
138+
.stream(sevIds)
139+
.map( sevs -> sevs.split(":"))
140+
.collect(Collectors.toMap(
141+
keyValue -> Long.parseLong(keyValue[1].trim()),
142+
keyValue -> new SevObj(
143+
keyValue[0].trim(),
144+
this.severities.get(keyValue[0].trim()),
145+
borders.get(keyValue[0].trim())
146+
),
147+
(v1,v2) -> v1,
148+
HashMap<Long, SevObj>::new
149+
));
82150

151+
this.totalLevels = colors.length;
83152
}
84153

85154
}
@@ -106,6 +175,14 @@ private Paint[] getColors() {
106175
return new Paint[] {new Color(Integer.parseInt(defaultColor.trim(), 16)) };
107176
}
108177
}
178+
private Paint getDefaultLabelColor() {
179+
String defaultColor = this.getConfigs().get("Default Label Color");
180+
if(defaultColor == null || defaultColor.trim().equals("")) {
181+
return new Color(0xFFFFFF) ; // Default Color
182+
}else {
183+
return new Color(Integer.parseInt(defaultColor.trim(), 16));
184+
}
185+
}
109186

110187
private int [] getDimensions() {
111188
int [] dimensions = {900, 600};
@@ -136,7 +213,7 @@ private String vulnSeverityChartImage(List<Vulnerability> vulns, String reportTe
136213
chart.getPlot().setBackgroundPaint( new Color(0,0,0,0));
137214
chart.getPlot().setBackgroundImageAlpha(0);
138215
// set bar colors
139-
final CategoryItemRenderer renderer = new CustomRenderer(this.getColors(), this.getDefaultColor());
216+
final CategoryItemRenderer renderer = new CustomRenderer(this.sevMap, this.showBorders, this.showNumbers, this.getDefaultColor()[0], this.getDefaultLabelColor());
140217
final CategoryPlot plot = chart.getCategoryPlot();
141218
//Match Font
142219
Font font = new Font("Arial", Font.PLAIN, 25);
@@ -145,6 +222,14 @@ private String vulnSeverityChartImage(List<Vulnerability> vulns, String reportTe
145222
// remove bar gradient and shaddow
146223
((BarRenderer) renderer).setBarPainter(new StandardBarPainter());
147224
((BarRenderer) renderer).setShadowVisible(false);
225+
((BarRenderer) renderer).setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator());
226+
if(this.showNumbers) {
227+
((BarRenderer) renderer).setDefaultItemLabelsVisible(true);
228+
ItemLabelPosition position = new ItemLabelPosition(ItemLabelAnchor.CENTER,
229+
TextAnchor.CENTER, TextAnchor.CENTER, 0.0);
230+
renderer.setDefaultPositiveItemLabelPosition(position);
231+
renderer.setDefaultItemLabelPaint(this.getDefaultLabelColor());
232+
}
148233
plot.setRenderer(renderer);
149234
// Show mininal gridlines
150235
plot.setRangeGridlinesVisible(false);
@@ -164,42 +249,133 @@ private String vulnSeverityChartImage(List<Vulnerability> vulns, String reportTe
164249
return image;
165250
}
166251

252+
private List<String>getSevNames() {
253+
return this.sevMap.keySet().stream()
254+
.map( id -> this.sevMap.get(id).name)
255+
.collect(Collectors.toList());
256+
}
257+
static Long [] getSevIds(HashMap<Long,SevObj> sevMap) {
258+
Long [] ids = (Long[]) sevMap.keySet().toArray(new Long[0]);
259+
Arrays.sort(ids, Collections.reverseOrder());
260+
return ids;
261+
}
262+
167263

168264
private CategoryDataset createDataset(List<Vulnerability> vulns) {
169265

170266
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
171-
String [] severityNames = (String[]) this.severities.keySet().toArray(new String[0]);
172-
for(int level=totalLevels-1; level >=0; level-- ) {
173-
final int index = level;
174-
long count = vulns.stream().filter(v -> v.getOverall().equals(new Long(index))).count();
175-
dataset.setValue(count, "Severity", severityNames[this.totalLevels - index -1]);
176-
}
267+
Arrays.asList(getSevIds(this.sevMap)).forEach(id -> {
268+
long count = vulns.stream().filter(v -> v.getOverall().equals(id)).count();
269+
dataset.setValue(count, this.yAxisName, this.sevMap.get(id).name);
270+
});
271+
177272
return dataset;
178273
}
179-
180274
private JFreeChart createChart(CategoryDataset dataset) {
181-
182-
JFreeChart barChart = ChartFactory.createBarChart("", "", "Severity", dataset, PlotOrientation.VERTICAL, false,
275+
String yAxisName = this.yAxisName;
276+
JFreeChart barChart = ChartFactory.createBarChart("", "", yAxisName, dataset, PlotOrientation.VERTICAL, false,
183277
true, false);
184278
return barChart;
185279
}
186280

187281
class CustomRenderer extends BarRenderer {
188282

189-
private Paint[] colors;
190-
private Paint[] defaultColor;
283+
private Paint defaultBarColor;
284+
private Paint labelColor;
285+
private HashMap<Long, SevObj> colorMap;
286+
private Boolean showBorders;
287+
private Boolean showNumbers;
191288

192-
public CustomRenderer(final Paint[] colors, final Paint[] defaultColor ) {
193-
this.colors = colors;
289+
public CustomRenderer(final HashMap<Long,SevObj> colorMap, Boolean showBorders, Boolean showNumbers, Paint defaultBarColor, Paint labelColor ) {
290+
this.colorMap = colorMap;
291+
this.showBorders = showBorders;
292+
this.defaultBarColor = defaultBarColor;
293+
this.labelColor = labelColor;
294+
this.showNumbers = showNumbers;
194295
}
195-
296+
@Override
196297
public Paint getItemPaint(final int row, final int column) {
197-
if (column >= this.colors.length) {
198-
Paint[] defaultPaint = this.defaultColor;
199-
return defaultPaint[0];
298+
if (column >= this.colorMap.keySet().size()) {
299+
Paint defaultPaint = this.defaultBarColor;
300+
return defaultPaint;
200301
}
201-
return this.colors[column % this.colors.length];
302+
Long [] sevIds = VulnerabilityBarChart.getSevIds(colorMap);
303+
Long id = sevIds[column];
304+
305+
306+
return sevMap.get(id).color;
202307
}
308+
@Override
309+
public void drawItem(Graphics2D g2,
310+
CategoryItemRendererState state,
311+
Rectangle2D dataArea,
312+
CategoryPlot plot,
313+
CategoryAxis domainAxis,
314+
ValueAxis rangeAxis,
315+
CategoryDataset dataset,
316+
int row,
317+
int column,
318+
int pass) {
319+
320+
// Only draw bars in pass 1
321+
if (pass != 0) {
322+
return;
323+
}
324+
325+
// Determine the bar's position and dimensions
326+
double value = dataset.getValue(row, column).doubleValue();
327+
double barStart = rangeAxis.valueToJava2D(
328+
rangeAxis.getLowerBound(), dataArea, plot.getRangeAxisEdge());
329+
double barEnd = rangeAxis.valueToJava2D(value, dataArea, plot.getRangeAxisEdge());
330+
double barWidth = state.getBarWidth();
331+
332+
// Calculate the bar's rectangle
333+
double x = domainAxis.getCategoryStart(column, getColumnCount(), dataArea, plot.getDomainAxisEdge())
334+
+ state.getBarWidth() * row;
335+
double y = Math.min(barStart, barEnd);
336+
double height = Math.abs(barEnd - barStart);
337+
338+
Rectangle2D bar = new Rectangle2D.Double(x, y, barWidth, height);
339+
340+
// Draw the bar (default rendering)
341+
g2.setPaint(getItemPaint(row, column));
342+
g2.fill(bar);
343+
344+
// Draw the border
345+
if(this.showBorders) {
346+
Long [] sevIds = VulnerabilityBarChart.getSevIds(colorMap);
347+
Long id = sevIds[column];
348+
Color c = sevMap.get(id).border;
349+
System.out.println(c);
350+
g2.setPaint(c);
351+
g2.setStroke(new BasicStroke(1.0f)); // Adjust stroke as needed
352+
}else {
353+
g2.setPaint(Color.BLACK); // Default border color
354+
g2.setStroke(new BasicStroke(0f)); // Adjust stroke as needed
355+
}
356+
g2.draw(bar);
357+
358+
if(this.showNumbers) {
359+
// Draw the item label (bar value) in the center of the bar
360+
int fontSize = 12; // Set your desired font size
361+
Font originalFont = g2.getFont(); // Save the original font
362+
Font newFont = originalFont.deriveFont((float) fontSize);
363+
g2.setFont(newFont);
364+
String label = String.valueOf( (int) value); // Convert the value to a string
365+
FontMetrics metrics = g2.getFontMetrics();
366+
double labelWidth = metrics.stringWidth(label);
367+
double labelHeight = metrics.getHeight();
368+
369+
// Calculate label position (center of the bar)
370+
double labelX = bar.getCenterX() - (labelWidth / 2);
371+
double labelY = bar.getCenterY() + (labelHeight / 4); // Adjust for vertical alignment
372+
373+
g2.setPaint(this.labelColor); // Set label color
374+
g2.drawString(label, (float) labelX, (float) labelY);
375+
g2.setFont(originalFont);
376+
}
377+
}
378+
203379
}
204380

205381

0 commit comments

Comments
 (0)