From 27f90e7e1f97603d4cbe5ef793e2811a19334775 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Fri, 19 Mar 2021 20:04:51 -0400 Subject: [PATCH] bar chart included --- .../ui/Bundle.properties-MERGED | 6 + .../datasourcesummary/ui/TimelinePanel.java | 46 ++++++- .../uiutils/BarChartExport.java | 120 ++++++++++-------- 3 files changed, 116 insertions(+), 56 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED index 3f82fcdd3f..bfd955692f 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED @@ -114,6 +114,12 @@ SizeRepresentationUtil_units_megabytes=MB SizeRepresentationUtil_units_petabytes=PB SizeRepresentationUtil_units_terabytes=TB TimelinePanel_earliestLabel_title=Earliest +TimelinePanel_getExports_activityRange=Activity Range +TimelinePanel_getExports_chartName=Last 30 Days +TimelinePanel_getExports_dateColumnHeader=Date +TimelinePanel_getExports_earliest=Earliest: +TimelinePanel_getExports_latest=Latest: +TimelinePanel_getExports_sheetName=Timeline TimelinePanel_latestLabel_title=Latest TimlinePanel_last30DaysChart_artifactEvts_title=Result Events TimlinePanel_last30DaysChart_fileEvts_title=File Events diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java index 030181c04b..d27d4f595d 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java @@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineDataSourceUtils import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.DailyActivityAmount; import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.TimelineSummaryData; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartExport; import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartPanel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartSeries; import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartPanel.OrderedKey; @@ -46,7 +47,12 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartSeries.BarChartIt import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetcher; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultCellModel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.KeyValueItemExportable; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.TitledExportable; import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent; import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableLabel; @@ -70,7 +76,9 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel { private static final Logger logger = Logger.getLogger(TimelinePanel.class.getName()); private static final long serialVersionUID = 1L; - private static final DateFormat EARLIEST_LATEST_FORMAT = getUtcFormat("MMM d, yyyy"); + + private static final String EARLIEST_LATEST_FORMAT_STR = "MMM d, yyyy"; + private static final DateFormat EARLIEST_LATEST_FORMAT = getUtcFormat(EARLIEST_LATEST_FORMAT_STR); private static final DateFormat CHART_FORMAT = getUtcFormat("MMM d, yyyy"); private static final int MOST_RECENT_DAYS_COUNT = 30; @@ -94,6 +102,8 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel { // all loadable components on this tab private final List> loadableComponents = Arrays.asList(earliestLabel, latestLabel, last30DaysChart); + private final DataFetcher dataFetcher; + // actions to load data for this tab private final List> dataFetchComponents; @@ -107,12 +117,11 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel { public TimelinePanel(TimelineSummary timelineData) { super(timelineData); + dataFetcher = (dataSource) -> timelineData.getData(dataSource, MOST_RECENT_DAYS_COUNT); + // set up data acquisition methods dataFetchComponents = Arrays.asList( - new DataFetchWorker.DataFetchComponents<>( - (dataSource) -> timelineData.getData(dataSource, MOST_RECENT_DAYS_COUNT), - (result) -> handleResult(result)) - ); + new DataFetchWorker.DataFetchComponents<>(dataFetcher, (result) -> handleResult(result))); initComponents(); } @@ -282,9 +291,34 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel { super.close(); } + private static DefaultCellModel getEarliestLatestCell(Date date) { + return new DefaultCellModel<>(date, (dt) -> dt == null ? "" : EARLIEST_LATEST_FORMAT.format(dt), EARLIEST_LATEST_FORMAT_STR); + } + + @Messages({ + "TimelinePanel_getExports_sheetName=Timeline", + "TimelinePanel_getExports_activityRange=Activity Range", + "TimelinePanel_getExports_earliest=Earliest:", + "TimelinePanel_getExports_latest=Latest:", + "TimelinePanel_getExports_dateColumnHeader=Date", + "TimelinePanel_getExports_chartName=Last 30 Days",}) @Override List getExports(DataSource dataSource) { - return Collections.emptyList(); + TimelineSummaryData summaryData = getFetchResult(dataFetcher, "Timeline", dataSource); + if (summaryData == null) { + return Collections.emptyList(); + } + + return Arrays.asList( + new ExcelSpecialFormatExport(Bundle.TimelinePanel_getExports_sheetName(), + Arrays.asList( + new TitledExportable(Bundle.TimelinePanel_getExports_activityRange(), Collections.emptyList()), + new KeyValueItemExportable(Bundle.TimelinePanel_getExports_earliest(), getEarliestLatestCell(summaryData.getMinDate())), + new KeyValueItemExportable(Bundle.TimelinePanel_getExports_latest(), getEarliestLatestCell(summaryData.getMaxDate())), + new BarChartExport(Bundle.TimelinePanel_getExports_dateColumnHeader(), + "#,###", + Bundle.TimelinePanel_getExports_chartName(), + parseChartData(summaryData.getMostRecentDaysActivity()))))); } /** diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartExport.java index 032912742b..08c01ee501 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartExport.java @@ -5,13 +5,13 @@ */ package org.sleuthkit.autopsy.datasourcesummary.uiutils; -import com.google.cloud.Tuple; import java.awt.Color; import java.nio.ByteBuffer; -import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Pair; import org.apache.poi.ss.usermodel.Sheet; @@ -31,8 +31,6 @@ import org.apache.poi.xddf.usermodel.chart.XDDFChartData; import org.apache.poi.xddf.usermodel.chart.XDDFChartLegend; import org.apache.poi.xddf.usermodel.chart.XDDFDataSource; import org.apache.poi.xddf.usermodel.chart.XDDFDataSourcesFactory; -import org.apache.poi.xddf.usermodel.chart.XDDFNumericalDataSource; -import org.apache.poi.xddf.usermodel.chart.XDDFPieChartData; import org.apache.poi.xddf.usermodel.chart.XDDFValueAxis; import org.apache.poi.xssf.usermodel.XSSFChart; import org.apache.poi.xssf.usermodel.XSSFClientAnchor; @@ -48,7 +46,8 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport. * @author gregd */ public class BarChartExport implements ExcelItemExportable, ExcelSheetExport { - private final ExcelTableExport tableExport; + + private final ExcelTableExport>, ? extends ExcelCellModel> tableExport; private final int colOffset; private final int rowPadding; private final int colSize; @@ -57,8 +56,8 @@ public class BarChartExport implements ExcelItemExportable, ExcelSheetExport { private final String sheetName; private final List categories; private final String keyColumnHeader; - - public BarChartExport(String keyColumnHeader, + + public BarChartExport(String keyColumnHeader, String valueFormatString, String chartTitle, List categories) { @@ -66,39 +65,59 @@ public class BarChartExport implements ExcelItemExportable, ExcelSheetExport { } public BarChartExport(String keyColumnHeader, String valueFormatString, - String chartTitle, String sheetName, - List categories, + String chartTitle, String sheetName, + List categories, int colOffset, int rowPadding, int colSize, int rowSize) { - + this.keyColumnHeader = keyColumnHeader; - - List categoryKeys = categories.stream() - .filter(cat -> cat != null && cat.getKey() != null) - .sorted((c1, c2) -> c1.getKey().compareTo(c2.getKey())) - .collect(Collectors.toList()); - - List> rowKeys = categories.stream() + + List rowKeys = categories.stream() .filter(cat -> cat != null && cat.getItems() != null) - .flatMap(cat -> cat.getItems().stream().map(item -> item.getKey())) - .filter(i -> i != null) - .distinct() - .sorted() + .map(cat -> cat.getItems()) + .max((items1, items2) -> Integer.compare(items1.size(), items2.size())) + .orElse(Collections.emptyList()) + .stream() + .map((barChartItem) -> barChartItem.getKey()) .collect(Collectors.toList()); - - Map, Comparable>, Double> valueMap = categories.stream() - .flatMap((cat) -> cat.getItems().stream().map((item) -> Tuple.of(Tuple.of(cat.getKey(), item.getKey()), item.getValue()))) + + // map of (category, item) -> value + Map, Double> valueMap = IntStream.range(0, categories.size()) + .mapToObj(idx -> Pair.of(idx, categories.get(idx))) + .filter(pair -> pair.getValue() != null && pair.getValue().getItems() != null) + .flatMap(categoryPair -> { + return IntStream.range(0, categoryPair.getValue().getItems().size()) + .mapToObj(idx -> Pair.of(idx, categoryPair.getValue().getItems().get(idx))) + .map(itemPair -> Pair.of( + Pair.of(categoryPair.getKey(), itemPair.getKey()), + itemPair.getValue() == null ? null : itemPair.getValue().getValue())); + }) .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue(), (v1, v2) -> v1)); - - List, List>> values = rowKeys.stream() - .map((rowValue)) - - this.tableExport = new ExcelTableExport<>(chartTitle, - Stream.concat(Stream.of(new ColumnModel<>(keyColumnHeader, (category) -> new DefaultCellModel<>(category.getLabel()))), ) - Arrays.asList( - , - new ColumnModel<>(valueColumnHeader, (category) -> new DefaultCellModel<>(category.getValue(), null, valueFormatString)) - ), - categories); + + List>> values = IntStream.range(0, rowKeys.size()) + .mapToObj(idx -> Pair.of(idx, rowKeys.get(idx))) + .map((rowPair) -> { + List items = IntStream.range(0, categories.size()) + .mapToObj(idx -> valueMap.get(Pair.of(idx, rowPair.getKey()))) + .collect(Collectors.toList()); + + return Pair.of(rowPair.getValue(), items); + }) + .collect(Collectors.toList()); + + ColumnModel>, DefaultCellModel> categoryColumn = new ColumnModel<>(keyColumnHeader, (row) -> new DefaultCellModel<>(row.getKey())); + + Stream>, DefaultCellModel>> dataColumns = IntStream.range(0, categories.size()) + .mapToObj(idx -> new ColumnModel<>( + categories.get(idx).getKey().toString(), + (row) -> new DefaultCellModel<>(row.getValue().get(idx)))); + + this.tableExport = new ExcelTableExport>, DefaultCellModel>( + chartTitle, + Stream.concat(Stream.of(categoryColumn), dataColumns) + .collect(Collectors.toList()), + values + ); + this.colOffset = colOffset; this.rowPadding = rowPadding; this.colSize = colSize; @@ -107,9 +126,7 @@ public class BarChartExport implements ExcelItemExportable, ExcelSheetExport { this.sheetName = sheetName; this.categories = categories; } - - - + @Override public String getSheetName() { return sheetName; @@ -125,16 +142,16 @@ public class BarChartExport implements ExcelItemExportable, ExcelSheetExport { if (!(sheet instanceof XSSFSheet)) { throw new ExcelExportException("Sheet must be an XSSFSheet in order to write."); } - + XSSFSheet xssfSheet = (XSSFSheet) sheet; - + // write pie chart table data ItemDimensions tableDimensions = tableExport.write(xssfSheet, rowStart + rowPadding, colStart, env); XSSFDrawing drawing = xssfSheet.createDrawingPatriarch(); - - int chartColStart = colStart + 2 + colOffset; - + + int chartColStart = colStart + categories.size() + 1 + colOffset; + //createAnchor(int dx1, int dy1, int dx2, int dy2, int col1, int row1, int col2, int row2); XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, chartColStart, rowStart + rowPadding, chartColStart + colSize, rowStart + rowSize); @@ -143,7 +160,6 @@ public class BarChartExport implements ExcelItemExportable, ExcelSheetExport { chart.setTitleOverlay(false); XDDFChartLegend legend = chart.getOrAddLegend(); legend.setPosition(LegendPosition.BOTTOM); - // Use a category axis for the bottom axis. XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM); @@ -156,13 +172,19 @@ public class BarChartExport implements ExcelItemExportable, ExcelSheetExport { XDDFBarChartData data = (XDDFBarChartData) chart.createData(ChartTypes.BAR, bottomAxis, leftAxis); data.setBarGrouping(BarGrouping.STACKED); - XDDFDataSource headerSource = XDDFDataSourcesFactory.fromStringCellRange(sheet, new CellRangeAddress(1, keyVals.size(), 0, 0)); + XDDFDataSource headerSource = XDDFDataSourcesFactory.fromStringCellRange(xssfSheet, + new CellRangeAddress(tableDimensions.getRowStart() + 1, tableDimensions.getRowEnd(), + tableDimensions.getColStart(), tableDimensions.getColStart())); + data.setBarDirection(BarDirection.COL); for (int i = 0; i < categories.size(); i++) { XDDFChartData.Series series = data.addSeries(headerSource, - XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(1, keyVals.size(), i + 1, i + 1))); - series.setTitle(categories.size() > i && categories.get(i).getIdentifier() != null ? categories.get(i).getIdentifier() : "", null); + XDDFDataSourcesFactory.fromNumericCellRange(xssfSheet, + new CellRangeAddress(tableDimensions.getRowStart() + 1, tableDimensions.getRowEnd(), + tableDimensions.getColStart() + 1 + i, tableDimensions.getColStart() + 1 + i))); + + series.setTitle(categories.size() > i && categories.get(i).getKey() != null ? categories.get(i).getKey().toString() : "", null); if (categories.get(i).getColor() != null) { Color color = categories.get(i).getColor(); byte[] colorArrARGB = ByteBuffer.allocate(4).putInt(color.getRGB()).array(); @@ -178,10 +200,8 @@ public class BarChartExport implements ExcelItemExportable, ExcelSheetExport { } chart.plot(data); - + return new ItemDimensions(rowStart, colStart, Math.max(tableDimensions.getRowEnd(), rowStart + rowSize) + rowPadding, chartColStart + colSize); } - - }