1 package net.sf.statcvs.charts;
2
3 import java.awt.Color;
4 import java.awt.Dimension;
5 import java.awt.Paint;
6 import java.util.ArrayList;
7 import java.util.Collection;
8 import java.util.Collections;
9 import java.util.Comparator;
10 import java.util.Date;
11 import java.util.HashMap;
12 import java.util.Iterator;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.SortedSet;
16
17 import net.sf.statcvs.Messages;
18 import net.sf.statcvs.model.Author;
19 import net.sf.statcvs.model.Directory;
20 import net.sf.statcvs.model.Module;
21 import net.sf.statcvs.model.Repository;
22 import net.sf.statcvs.model.Revision;
23 import net.sf.statcvs.output.ReportConfig;
24 import net.sf.statcvs.pages.HTML;
25 import net.sf.statcvs.reports.LOCSeriesBuilder;
26 import net.sf.statcvs.util.IntegerMap;
27
28 import org.jfree.chart.ChartFactory;
29 import org.jfree.chart.JFreeChart;
30 import org.jfree.chart.annotations.XYAnnotation;
31 import org.jfree.chart.axis.DateAxis;
32 import org.jfree.chart.axis.ValueAxis;
33 import org.jfree.chart.plot.XYPlot;
34 import org.jfree.chart.renderer.xy.XYItemRenderer;
35 import org.jfree.chart.renderer.xy.XYStepRenderer;
36 import org.jfree.data.time.TimeSeries;
37 import org.jfree.data.time.TimeSeriesCollection;
38
39 /**
40 * Produces Lines Of Code charts
41 *
42 * TODO: At least the single-series charts should be done by TimeLineChartMakers
43 *
44 * @author jentzsch
45 * @author Richard Cyganiak (richard@cyganiak.de)
46 * @version $Id: LOCChartMaker.java,v 1.19 2009/08/31 19:16:35 benoitx Exp $
47 */
48 public class LOCChartMaker {
49 private final ReportConfig config;
50 private ChartImage chartFile = null;
51 private final String chartName;
52
53 /**
54 * Creates a Lines Of Code chart from a <tt>BasicTimeSeries</tt> and
55 * saves it as PNG
56 * @param locSeries the LOC history
57 * @param title the chart title
58 * @param fileName the filename where the chart will be saved
59 * @param size width and height of PNG in pixels
60 * @param annotations
61 */
62 public LOCChartMaker(final String chartName, final ReportConfig config, final TimeSeries locSeries, final String title, final String fileName,
63 final Dimension size, final List annotations) {
64 this.chartName = chartName;
65 this.config = config;
66 if (locSeries == null) {
67 return;
68 }
69 final Paint[] colors = new Paint[1];
70 colors[0] = Color.RED;
71
72 final TimeSeriesCollection collection = new TimeSeriesCollection();
73 collection.addSeries(locSeries);
74 final JFreeChart chart = createLOCChart(collection, colors, title, annotations);
75 final Dimension dim = ChartConfigUtil.getDimension(chartName, size);
76 this.chartFile = this.config.createChartImage(fileName, title, chart, dim);
77 }
78
79 /**
80 * Creates a Lines Of Code chart from a list of <tt>BasicTimesSeries</tt> and
81 * saves it as PNG
82 * @param locSeriesList a list of <tt>BasicTimesSeries</tt>
83 * @param title the chart title
84 * @param fileName the filename where the chart will be saved
85 * @param size width and height of PNG in pixels
86 */
87 public LOCChartMaker(final String chartName, final ReportConfig config, final List locSeriesList, final String title, final String fileName,
88 final Dimension size, final List annotations) {
89 this.chartName = chartName;
90 this.config = config;
91 if (locSeriesList.isEmpty()) {
92 return;
93 }
94 int i = 0;
95 final TimeSeriesCollection collection = new TimeSeriesCollection();
96 final Iterator it = locSeriesList.iterator();
97 while (it.hasNext()) {
98 final TimeSeries series = (TimeSeries) it.next();
99 collection.addSeries(series);
100 i++;
101 }
102 final JFreeChart chart = createLOCChart(collection, null, title, annotations);
103 final Dimension dim = ChartConfigUtil.getDimension(chartName, size);
104
105 this.chartFile = this.config.createChartImage(fileName, title, chart, dim);
106 }
107
108 private JFreeChart createLOCChart(final TimeSeriesCollection data, final Paint[] colors, final String title, final List annotations) {
109 final String domain = Messages.getString("TIME_LOC_DOMAIN");
110 final String range = Messages.getString("TIME_LOC_RANGE");
111
112 final boolean legend = (data.getSeriesCount() > 1);
113 final JFreeChart chart = ChartFactory.createTimeSeriesChart(this.config.getProjectName() + ": " + title, domain, range, data, legend, false, false);
114
115 final XYPlot plot = chart.getXYPlot();
116 plot.setRenderer(new XYStepRenderer());
117 if (colors == null) {
118
119 for (int i = 0; i < plot.getSeriesCount(); i++) {
120 final Paint seriesPaint = plot.getRenderer().getSeriesPaint(i);
121 if (seriesPaint != null && seriesPaint.equals(new Color(0xFF, 0xFF, 0x55))) {
122 plot.getRenderer().setSeriesPaint(i, new Color(240, 220, 0x55));
123 }
124 }
125 } else {
126 for (int i = 0; i < colors.length; i++) {
127 plot.getRenderer().setSeriesPaint(i, colors[i]);
128 }
129 }
130 final DateAxis domainAxis = (DateAxis) plot.getDomainAxis();
131 domainAxis.setVerticalTickLabels(true);
132 final ValueAxis valueAxis = plot.getRangeAxis();
133 valueAxis.setLowerBound(0);
134
135 if (annotations != null) {
136 for (final Iterator it = annotations.iterator(); it.hasNext();) {
137 plot.addAnnotation((XYAnnotation) it.next());
138 }
139 }
140
141 plot.setBackgroundPaint(ChartConfigUtil.getPlotColor(chartName));
142 chart.setBackgroundPaint(ChartConfigUtil.getBackgroundColor(chartName));
143 final XYItemRenderer renderer = plot.getRenderer();
144 ChartConfigUtil.configureStroke(chartName, renderer, data);
145 ChartConfigUtil.configureShapes(chartName, renderer);
146 ChartConfigUtil.configureCopyrightNotice(chartName, chart);
147 ChartConfigUtil.configureChartBackgroungImage(chartName, chart);
148 ChartConfigUtil.configurePlotImage(chartName, chart);
149
150 return chart;
151 }
152
153 public ChartImage toFile() {
154 return this.chartFile;
155 }
156
157 private static TimeSeries getLOCTimeSeries(final SortedSet revisions, final String title) {
158 final LOCSeriesBuilder locCounter = new LOCSeriesBuilder(title, true);
159 final Iterator it = revisions.iterator();
160 while (it.hasNext()) {
161 locCounter.addRevision((Revision) it.next());
162 }
163 if (locCounter.getMaximum() == 0) {
164 return null;
165 }
166 return locCounter.getTimeSeries();
167 }
168
169 public static class MainLOCChartMaker extends LOCChartMaker {
170 public MainLOCChartMaker(final String chartName, final ReportConfig config, final String fileName, final Dimension size) {
171 super(chartName, config, getLOCTimeSeries(config.getRepository().getRevisions(), Messages.getString("TIME_LOC_SUBTITLE")), Messages
172 .getString("TIME_LOC_SUBTITLE"), fileName, size, SymbolicNameAnnotation.createAnnotations(config.getRepository().getSymbolicNames()));
173 }
174 }
175
176 public static class DirectoryLOCChartMaker extends LOCChartMaker {
177 private static String getTitle(final Directory directory) {
178 return directory.getPath() + (directory.getPath() != null && directory.getPath().length() > 1 ? " " : "") + Messages.getString("TIME_LOC_SUBTITLE");
179 }
180
181 private static String getFilename(final Directory directory) {
182 return "loc_module" + HTML.escapeDirectoryName(directory.getPath()) + ".png";
183 }
184
185 public DirectoryLOCChartMaker(final ReportConfig config, final Directory directory) {
186 super("loc_module", config, getLOCTimeSeries(directory.getRevisions(), getTitle(directory)), getTitle(directory), getFilename(directory), config
187 .getLargeChartSize(), SymbolicNameAnnotation.createAnnotations(config.getRepository().getSymbolicNames()));
188 }
189 }
190
191 public static class AllDevelopersLOCChartMaker extends LOCChartMaker {
192 private static List createAllDevelopersLOCSeries(final ReportConfig config) {
193 Iterator it = config.getRepository().getAuthors().iterator();
194 final Map authorSeriesMap = new HashMap();
195 while (it.hasNext()) {
196 final Author author = (Author) it.next();
197 if (!config.isDeveloper(author)) {
198 continue;
199 }
200 authorSeriesMap.put(author, new LOCSeriesBuilder(author.getRealName(), false));
201 }
202 it = config.getRepository().getRevisions().iterator();
203 while (it.hasNext()) {
204 final Revision rev = (Revision) it.next();
205 if (rev.isBeginOfLog()) {
206 continue;
207 }
208 final LOCSeriesBuilder builder = (LOCSeriesBuilder) authorSeriesMap.get(rev.getAuthor());
209 if (builder != null) {
210 builder.addRevision(rev);
211 }
212 }
213 final List authors = new ArrayList(authorSeriesMap.keySet());
214 Collections.sort(authors);
215 final List result = new ArrayList();
216 it = authors.iterator();
217 while (it.hasNext()) {
218 final Author author = (Author) it.next();
219 final LOCSeriesBuilder builder = (LOCSeriesBuilder) authorSeriesMap.get(author);
220 final TimeSeries series = builder.getTimeSeries();
221 if (series != null) {
222 result.add(series);
223 }
224 }
225 return result;
226 }
227
228 public AllDevelopersLOCChartMaker(final ReportConfig config, final Dimension size) {
229 super("loc_per_author", config, createAllDevelopersLOCSeries(config), Messages.getString("CONTRIBUTED_LOC_TITLE"), "loc_per_author.png", size,
230 SymbolicNameAnnotation.createAnnotations(config.getRepository().getSymbolicNames()));
231 }
232 }
233
234 public static class AllDirectoriesLOCChartMaker extends LOCChartMaker {
235 private static Collection getMajorDirectories(final Repository repository, final int max) {
236 if (repository.getFirstDate() == null || repository.getLastDate() == null || repository.getFirstDate().equals(repository.getLastDate())) {
237 return Collections.EMPTY_LIST;
238 }
239 final IntegerMap importances = new IntegerMap();
240 final Iterator it = repository.getDirectories().iterator();
241 while (it.hasNext()) {
242 final Directory directory = (Directory) it.next();
243 importances.put(directory, getImportance(directory, repository.getFirstDate(), repository.getLastDate()));
244 }
245 final List result = new ArrayList(repository.getDirectories());
246 Collections.sort(result, new Comparator() {
247 public int compare(final Object o1, final Object o2) {
248 final int importance1 = importances.get(o1);
249 final int importance2 = importances.get(o2);
250 if (importance1 > importance2) {
251 return -1;
252 }
253 if (importance1 == importance2) {
254 return 0;
255 }
256 return 1;
257 }
258 });
259 return firstN(result, max);
260 }
261
262 private static int getImportance(final Directory dir, final Date start, final Date end) {
263 final long timeRange = end.getTime() - start.getTime();
264 double maxImportance = 0;
265 int currentLines = 0;
266 final Iterator it = dir.getRevisions().iterator();
267 while (it.hasNext()) {
268 final Revision revision = (Revision) it.next();
269 currentLines += revision.getLinesDelta();
270 final long timeInRange = revision.getDate().getTime() - start.getTime();
271 final double timeFraction = (timeInRange / (double) timeRange) * 0.9 + 0.1;
272 maxImportance = Math.max(maxImportance, (currentLines) * (timeFraction));
273 }
274 return (int) (maxImportance * 10);
275 }
276
277 private static List firstN(final List list, final int n) {
278 return list.subList(0, Math.min(list.size(), n));
279 }
280
281 private static List createAllDirectoriesLOCSeries(final Repository repository, final int max) {
282 Iterator it = getMajorDirectories(repository, max).iterator();
283 final Map directorySeriesMap = new HashMap();
284 while (it.hasNext()) {
285 final Directory directory = (Directory) it.next();
286 directorySeriesMap.put(directory, new LOCSeriesBuilder(directory.getPath(), true));
287 }
288 it = repository.getRevisions().iterator();
289 while (it.hasNext()) {
290 final Revision rev = (Revision) it.next();
291 if (rev.isBeginOfLog()) {
292 continue;
293 }
294 final LOCSeriesBuilder builder = (LOCSeriesBuilder) directorySeriesMap.get(rev.getFile().getDirectory());
295 if (builder == null) {
296 continue;
297 }
298 builder.addRevision(rev);
299 }
300 final List directories = new ArrayList(directorySeriesMap.keySet());
301 Collections.sort(directories);
302 final List result = new ArrayList();
303 it = directories.iterator();
304 while (it.hasNext()) {
305 final Directory directory = (Directory) it.next();
306 final LOCSeriesBuilder builder = (LOCSeriesBuilder) directorySeriesMap.get(directory);
307 final TimeSeries series = builder.getTimeSeries();
308 if (series != null) {
309 result.add(series);
310 }
311 }
312 return result;
313 }
314
315 public AllDirectoriesLOCChartMaker(final ReportConfig config, final int showMaxDirectories) {
316 super("directories_loc_timeline", config, createAllDirectoriesLOCSeries(config.getRepository(), showMaxDirectories), Messages
317 .getString("DIRECTORY_LOC_TITLE"), "directories_loc_timeline.png", config.getLargeChartSize(), SymbolicNameAnnotation
318 .createAnnotations(config.getRepository().getSymbolicNames()));
319 }
320 }
321
322
323 public static class AllModulesLOCChartMaker extends LOCChartMaker {
324 private static Collection getMajorModules(final Repository repository, final int max) {
325 if (repository.getFirstDate() == null || repository.getLastDate() == null || repository.getFirstDate().equals(repository.getLastDate())) {
326 return Collections.EMPTY_LIST;
327 }
328 final IntegerMap importances = new IntegerMap();
329 final Iterator it = repository.getModules().values().iterator();
330 while (it.hasNext()) {
331 final Module directory = (Module) it.next();
332 importances.put(directory, getImportance(directory, repository.getFirstDate(), repository.getLastDate()));
333 }
334 final List result = new ArrayList(repository.getModules().values());
335 Collections.sort(result, new Comparator() {
336 public int compare(final Object o1, final Object o2) {
337 final int importance1 = importances.get(o1);
338 final int importance2 = importances.get(o2);
339 if (importance1 > importance2) {
340 return -1;
341 }
342 if (importance1 == importance2) {
343 return 0;
344 }
345 return 1;
346 }
347 });
348 return firstN(result, max);
349 }
350
351 private static int getImportance(final Module dir, final Date start, final Date end) {
352 final long timeRange = end.getTime() - start.getTime();
353 double maxImportance = 0;
354 int currentLines = 0;
355 final Iterator it = dir.getRevisions().iterator();
356 while (it.hasNext()) {
357 final Revision revision = (Revision) it.next();
358 currentLines += revision.getLinesDelta();
359 final long timeInRange = revision.getDate().getTime() - start.getTime();
360 final double timeFraction = (timeInRange / (double) timeRange) * 0.9 + 0.1;
361 maxImportance = Math.max(maxImportance, (currentLines) * (timeFraction));
362 }
363 return (int) (maxImportance * 10);
364 }
365
366 private static List firstN(final List list, final int n) {
367 return list.subList(0, Math.min(list.size(), n));
368 }
369
370 private static List createAllModulesLOCSeries(final Repository repository, final int max) {
371 Iterator it = getMajorModules(repository, max).iterator();
372 final Map directorySeriesMap = new HashMap();
373 while (it.hasNext()) {
374 final Module module = (Module) it.next();
375 directorySeriesMap.put(module, new LOCSeriesBuilder(module.getName(), true));
376 }
377 it = repository.getRevisions().iterator();
378 while (it.hasNext()) {
379 final Revision rev = (Revision) it.next();
380 if (rev.isBeginOfLog()) {
381 continue;
382 }
383 final LOCSeriesBuilder builder = (LOCSeriesBuilder) directorySeriesMap.get(rev.getFile().getModule());
384 if (builder == null) {
385 continue;
386 }
387 builder.addRevision(rev);
388 }
389 final List modules = new ArrayList(directorySeriesMap.keySet());
390 Collections.sort(modules);
391 final List result = new ArrayList();
392 it = modules.iterator();
393 while (it.hasNext()) {
394 final Module module = (Module) it.next();
395 final LOCSeriesBuilder builder = (LOCSeriesBuilder) directorySeriesMap.get(module);
396 final TimeSeries series = builder.getTimeSeries();
397 if (series != null) {
398 result.add(series);
399 }
400 }
401 return result;
402 }
403
404 public AllModulesLOCChartMaker(final ReportConfig config, final int showMaxModules) {
405 super("modules_loc_timeline", config, createAllModulesLOCSeries(config.getRepository(), showMaxModules), Messages
406 .getString("MODULES_LOC_TITLE"), "modules_loc_timeline.png", config.getLargeChartSize(), SymbolicNameAnnotation
407 .createAnnotations(config.getRepository().getSymbolicNames()));
408 }
409 }
410 }