19 package org.sleuthkit.autopsy.report.modules.datasourcesummaryexport;
 
   21 import java.awt.Color;
 
   22 import java.nio.ByteBuffer;
 
   23 import java.util.Collections;
 
   24 import java.util.List;
 
   26 import java.util.stream.Collectors;
 
   27 import java.util.stream.IntStream;
 
   28 import java.util.stream.Stream;
 
   29 import org.apache.commons.lang3.tuple.Pair;
 
   30 import org.apache.poi.ss.usermodel.Sheet;
 
   31 import org.apache.poi.ss.util.CellRangeAddress;
 
   32 import org.apache.poi.xddf.usermodel.XDDFColor;
 
   33 import org.apache.poi.xddf.usermodel.XDDFShapeProperties;
 
   34 import org.apache.poi.xddf.usermodel.XDDFSolidFillProperties;
 
   35 import org.apache.poi.xddf.usermodel.chart.AxisCrosses;
 
   36 import org.apache.poi.xddf.usermodel.chart.AxisPosition;
 
   37 import org.apache.poi.xddf.usermodel.chart.BarDirection;
 
   38 import org.apache.poi.xddf.usermodel.chart.BarGrouping;
 
   39 import org.apache.poi.xddf.usermodel.chart.ChartTypes;
 
   40 import org.apache.poi.xddf.usermodel.chart.LegendPosition;
 
   41 import org.apache.poi.xddf.usermodel.chart.XDDFBarChartData;
 
   42 import org.apache.poi.xddf.usermodel.chart.XDDFCategoryAxis;
 
   43 import org.apache.poi.xddf.usermodel.chart.XDDFChartData;
 
   44 import org.apache.poi.xddf.usermodel.chart.XDDFChartLegend;
 
   45 import org.apache.poi.xddf.usermodel.chart.XDDFDataSource;
 
   46 import org.apache.poi.xddf.usermodel.chart.XDDFDataSourcesFactory;
 
   47 import org.apache.poi.xddf.usermodel.chart.XDDFValueAxis;
 
   48 import org.apache.poi.xssf.usermodel.XSSFChart;
 
   49 import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
 
   50 import org.apache.poi.xssf.usermodel.XSSFDrawing;
 
   51 import org.apache.poi.xssf.usermodel.XSSFSheet;
 
   61 class BarChartExport 
implements ExcelItemExportable, ExcelSheetExport {
 
   74     private static ExcelTableExport<Pair<Object, List<Double>>, ? extends CellModel> getTableModel(
 
   75             List<BarChartSeries> categories, String keyColumnHeader, String chartTitle) {
 
   79         List<? extends Object> rowKeys = categories.stream()
 
   80                 .filter(cat -> cat != null && cat.getItems() != null)
 
   81                 .map(cat -> cat.getItems())
 
   82                 .max((items1, items2) -> Integer.compare(items1.size(), items2.size()))
 
   83                 .orElse(Collections.emptyList())
 
   85                 .map((barChartItem) -> barChartItem.getKey())
 
   86                 .collect(Collectors.toList());
 
   89         Map<Pair<Integer, Integer>, Double> valueMap = IntStream.range(0, categories.size())
 
   90                 .mapToObj(idx -> Pair.of(idx, categories.get(idx)))
 
   91                 .filter(pair -> pair.getValue() != null && pair.getValue().getItems() != null)
 
   92                 .flatMap(categoryPair -> {
 
   93                     return IntStream.range(0, categoryPair.getValue().getItems().size())
 
   94                             .mapToObj(idx -> Pair.of(idx, categoryPair.getValue().getItems().get(idx)))
 
   95                             .map(itemPair -> Pair.of(
 
   96                             Pair.of(categoryPair.getKey(), itemPair.getKey()),
 
   97                             itemPair.getValue() == null ? null : itemPair.getValue().getValue()));
 
   99                 .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue(), (v1, v2) -> v1));
 
  103         List<Pair<Object, List<Double>>> values = IntStream.range(0, rowKeys.size())
 
  104                 .mapToObj(idx -> Pair.of(idx, rowKeys.get(idx)))
 
  106                     List<Double> items = IntStream.range(0, categories.size())
 
  107                             .mapToObj(idx -> valueMap.get(Pair.of(idx, rowPair.getKey())))
 
  108                             .collect(Collectors.toList());
 
  110                     return Pair.of(rowPair.getValue(), items);
 
  112                 .collect(Collectors.toList());
 
  115         ColumnModel<Pair<Object, List<Double>>, DefaultCellModel<?>> categoryColumn
 
  116                 = 
new ColumnModel<>(keyColumnHeader, (row) -> 
new DefaultCellModel<>(row.getKey()));
 
  119         Stream<ColumnModel<Pair<Object, List<Double>>, DefaultCellModel<?>>> dataColumns = IntStream.range(0, categories.size())
 
  120                 .mapToObj(idx -> 
new ColumnModel<>(
 
  121                 categories.get(idx).getKey().toString(),
 
  122                 (row) -> 
new DefaultCellModel<>(row.getValue().get(idx))));
 
  125         return new ExcelTableExport<Pair<Object, List<Double>>, DefaultCellModel<?>>(
 
  127                 Stream.concat(Stream.of(categoryColumn), dataColumns)
 
  128                         .collect(Collectors.toList()),
 
  133     private static final int DEFAULT_ROW_SIZE = 15;
 
  134     private static final int DEFAULT_COL_SIZE = 10;
 
  135     private static final int DEFAULT_ROW_PADDING = 1;
 
  136     private static final int DEFAULT_COL_OFFSET = 1;
 
  138     private final ExcelTableExport<Pair<Object, List<Double>>, ? extends CellModel> tableExport;
 
  139     private final int colOffset;
 
  140     private final int rowPadding;
 
  141     private final int colSize;
 
  142     private final int rowSize;
 
  143     private final String chartTitle;
 
  144     private final String sheetName;
 
  145     private final List<BarChartSeries> categories;
 
  146     private final String keyColumnHeader;
 
  158     BarChartExport(String keyColumnHeader,
 
  159             String valueFormatString,
 
  161             List<BarChartSeries> categories) {
 
  162         this(keyColumnHeader, valueFormatString, chartTitle, chartTitle, categories,
 
  163                 DEFAULT_COL_OFFSET, DEFAULT_ROW_PADDING, DEFAULT_COL_SIZE, DEFAULT_ROW_SIZE);
 
  181     BarChartExport(String keyColumnHeader, String valueFormatString,
 
  182             String chartTitle, String sheetName,
 
  183             List<BarChartSeries> categories,
 
  184             int colOffset, 
int rowPadding, 
int colSize, 
int rowSize) {
 
  186         this.keyColumnHeader = keyColumnHeader;
 
  187         this.tableExport = getTableModel(categories, keyColumnHeader, chartTitle);
 
  188         this.colOffset = colOffset;
 
  189         this.rowPadding = rowPadding;
 
  190         this.colSize = colSize;
 
  191         this.rowSize = rowSize;
 
  192         this.chartTitle = chartTitle;
 
  193         this.sheetName = sheetName;
 
  194         this.categories = categories;
 
  198     public String getSheetName() {
 
  203     public void renderSheet(Sheet sheet, ExcelExport.WorksheetEnv env) 
throws ExcelExport.ExcelExportException {
 
  204         write(sheet, 0, 0, env);
 
  208     public ItemDimensions write(Sheet sheet, 
int rowStart, 
int colStart, ExcelExport.WorksheetEnv env) 
throws ExcelExportException {
 
  209         if (!(sheet instanceof XSSFSheet)) {
 
  210             throw new ExcelExportException(
"Sheet must be an XSSFSheet in order to write.");
 
  213         XSSFSheet xssfSheet = (XSSFSheet) sheet;
 
  216         ItemDimensions tableDimensions = tableExport.write(xssfSheet, rowStart + rowPadding, colStart, env);
 
  218         XSSFDrawing drawing = xssfSheet.createDrawingPatriarch();
 
  220         int chartColStart = colStart + categories.size() + 1 + colOffset;
 
  223         XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, chartColStart, rowStart + rowPadding, chartColStart + colSize + 1, rowStart + rowSize + 1);
 
  225         XSSFChart chart = drawing.createChart(anchor);
 
  226         chart.setTitleText(chartTitle);
 
  227         chart.setTitleOverlay(
false);
 
  228         XDDFChartLegend legend = chart.getOrAddLegend();
 
  229         legend.setPosition(LegendPosition.BOTTOM);
 
  232         XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
 
  233         bottomAxis.setTitle(keyColumnHeader);
 
  234         XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
 
  235         leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
 
  236         leftAxis.setVisible(
false);
 
  238         XDDFBarChartData data = (XDDFBarChartData) chart.createData(ChartTypes.BAR, bottomAxis, leftAxis);
 
  239         data.setBarGrouping(BarGrouping.STACKED);
 
  241         XDDFDataSource<String> headerSource = XDDFDataSourcesFactory.fromStringCellRange(xssfSheet,
 
  242                 new CellRangeAddress(tableDimensions.getRowStart() + 1, tableDimensions.getRowEnd(),
 
  243                         tableDimensions.getColStart(), tableDimensions.getColStart()));
 
  245         data.setBarDirection(BarDirection.COL);
 
  248         for (
int i = 0; i < categories.size(); i++) {
 
  249             XDDFChartData.Series series = data.addSeries(headerSource,
 
  250                     XDDFDataSourcesFactory.fromNumericCellRange(xssfSheet,
 
  251                             new CellRangeAddress(tableDimensions.getRowStart() + 1, tableDimensions.getRowEnd(),
 
  252                                     tableDimensions.getColStart() + 1 + i, tableDimensions.getColStart() + 1 + i)));
 
  254             series.setTitle(categories.size() > i && categories.get(i).getKey() != null ? categories.get(i).getKey().toString() : 
"", null);
 
  255             if (categories.get(i).getColor() != null) {
 
  256                 Color color = categories.get(i).getColor();
 
  257                 byte[] colorArrARGB = ByteBuffer.allocate(4).putInt(color.getRGB()).array();
 
  258                 byte[] colorArrRGB = 
new byte[]{colorArrARGB[1], colorArrARGB[2], colorArrARGB[3]};
 
  259                 XDDFSolidFillProperties fill = 
new XDDFSolidFillProperties(XDDFColor.from(colorArrRGB));
 
  260                 XDDFShapeProperties properties = series.getShapeProperties();
 
  261                 if (properties == null) {
 
  262                     properties = 
new XDDFShapeProperties();
 
  264                 properties.setFillProperties(fill);
 
  265                 series.setShapeProperties(properties);
 
  271         return new ItemDimensions(rowStart, colStart, Math.max(tableDimensions.getRowEnd(), rowStart + rowSize) + rowPadding, chartColStart + colSize);