diff --git a/Core/build.xml b/Core/build.xml
index e8966c2508..40b8e966a3 100644
--- a/Core/build.xml
+++ b/Core/build.xml
@@ -89,6 +89,11 @@
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/OsAccountDataPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/OsAccountDataPanel.java
index f5f07d658b..82c267abff 100755
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/OsAccountDataPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/OsAccountDataPanel.java
@@ -191,11 +191,10 @@ public class OsAccountDataPanel extends JPanel {
private SectionData buildRealmProperties(OsAccountRealm realm) {
SectionData data = new SectionData(Bundle.OsAccountDataPanel_realm_title());
- Optional optional = realm.getRealmName();
- data.addData(Bundle.OsAccountDataPanel_realm_name(),
- optional.isPresent() ? optional.get() : Bundle.OsAccountDataPanel_realm_unknown());
+ String realmName = realm.getRealmNames().isEmpty() ? Bundle.OsAccountDataPanel_realm_unknown() : realm.getRealmNames().get(0);
+ data.addData(Bundle.OsAccountDataPanel_realm_name(), realmName);
- optional = realm.getRealmAddr();
+ Optional optional = realm.getRealmAddr();
data.addData(Bundle.OsAccountDataPanel_realm_address(),
optional.isPresent() ? optional.get() : "");
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java
index 94f15589a0..29e36b182a 100755
--- a/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java
@@ -256,12 +256,12 @@ public final class OsAccounts implements AutopsyVisitableItem {
Bundle.OsAccounts_loginNameProperty_desc(),
optional.isPresent() ? optional.get() : ""));
- optional = account.getRealm().getRealmName();
+ String realmName = account.getRealm().getRealmNames().isEmpty() ? "" : account.getRealm().getRealmNames().get(0);
propertiesSet.put(new NodeProperty<>(
Bundle.OsAccounts_accountRealmNameProperty_name(),
Bundle.OsAccounts_accountRealmNameProperty_displayName(),
Bundle.OsAccounts_accountRealmNameProperty_desc(),
- optional.isPresent() ? optional.get() : ""));
+ realmName));
Optional creationTimeValue = account.getCreationTime();
String timeDisplayStr
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 4a47bcb092..a7b3c8870c 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED
@@ -76,6 +76,8 @@ ExcelExportAction_exportToXLSX_gatheringTabData=Fetching Data for {0} Tab...
ExcelExportAction_exportToXLSX_writingToFile=Writing to File...
ExcelExportAction_getXLSXPath_directory=DataSourceSummary
ExcelExportAction_moduleName=Data Source Summary
+ExcelExportAction_runXLSXExport_errorMessage=There was an error while exporting.
+ExcelExportAction_runXLSXExport_errorTitle=Error While Exporting
ExcelExportAction_runXLSXExport_progressCancelActionTitle=Cancelling...
ExcelExportAction_runXLSXExport_progressCancelTitle=Cancel
# {0} - dataSource
@@ -114,6 +116,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
@@ -128,6 +136,7 @@ TypesPanel_fileMimeTypesChart_notAnalyzed_title=Not Analyzed
TypesPanel_fileMimeTypesChart_other_title=Other
TypesPanel_fileMimeTypesChart_title=File Types
TypesPanel_fileMimeTypesChart_unknown_title=Unknown
+TypesPanel_fileMimeTypesChart_valueLabel=Count
TypesPanel_fileMimeTypesChart_videos_title=Videos
TypesPanel_filesByCategoryTable_allocatedRow_title=Allocated Files
TypesPanel_filesByCategoryTable_directoryRow_title=Directories
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ExcelExportAction.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ExcelExportAction.java
index 3f3ac09ec3..22e06c07ed 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ExcelExportAction.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ExcelExportAction.java
@@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.datasourcesummary.ui;
-import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
@@ -34,6 +33,7 @@ import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.logging.Level;
+import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import org.openide.util.NbBundle;
@@ -186,7 +186,9 @@ class ExcelExportAction implements Consumer {
"# {0} - dataSource",
"ExcelExportAction_runXLSXExport_progressTitle=Exporting {0} to XLSX",
"ExcelExportAction_runXLSXExport_progressCancelTitle=Cancel",
- "ExcelExportAction_runXLSXExport_progressCancelActionTitle=Cancelling..."
+ "ExcelExportAction_runXLSXExport_progressCancelActionTitle=Cancelling...",
+ "ExcelExportAction_runXLSXExport_errorTitle=Error While Exporting",
+ "ExcelExportAction_runXLSXExport_errorMessage=There was an error while exporting.",
})
private void runXLSXExport(DataSource dataSource, File path) {
@@ -213,6 +215,10 @@ class ExcelExportAction implements Consumer {
get();
} catch (ExecutionException ex) {
logger.log(Level.WARNING, "Error while trying to export data source summary to xlsx.", ex);
+ JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
+ Bundle.ExcelExportAction_runXLSXExport_errorMessage(),
+ Bundle.ExcelExportAction_runXLSXExport_errorTitle(),
+ JOptionPane.ERROR_MESSAGE);
} catch (InterruptedException | CancellationException ex) {
// no op on cancellation
} finally {
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java
index ff30a72331..94af4b7545 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TimelinePanel.java
@@ -39,14 +39,20 @@ 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.BarChartPanel.BarChartItem;
-import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartPanel.BarChartSeries;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartSeries;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartPanel.OrderedKey;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartSeries.BarChartItem;
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();
}
@@ -138,9 +147,11 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
* data to be displayed as a bar chart.
*
* @param recentDaysActivity The data retrieved from TimelineSummary.
+ * @param showIntermediateDates If true, shows all dates. If false, shows
+ * only first and last date.
* @return The data to be displayed in the BarChart.
*/
- private List parseChartData(List recentDaysActivity) {
+ private List parseChartData(List recentDaysActivity, boolean showIntermediateDates) {
// if no data, return null indicating no result.
if (CollectionUtils.isEmpty(recentDaysActivity)) {
return null;
@@ -155,7 +166,7 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
long fileAmt = curItem.getFileActivityCount();
long artifactAmt = curItem.getArtifactActivityCount() * 100;
- String formattedDate = (i == 0 || i == recentDaysActivity.size() - 1)
+ String formattedDate = (showIntermediateDates || i == 0 || i == recentDaysActivity.size() - 1)
? formatDate(curItem.getDay(), CHART_FORMAT) : "";
OrderedKey thisKey = new OrderedKey(formattedDate, i);
@@ -182,7 +193,7 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
private void handleResult(DataFetchResult result) {
earliestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMinDate(), EARLIEST_LATEST_FORMAT)));
latestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMaxDate(), EARLIEST_LATEST_FORMAT)));
- last30DaysChart.showDataFetchResult(DataFetchResult.getSubResult(result, r -> parseChartData(r.getMostRecentDaysActivity())));
+ last30DaysChart.showDataFetchResult(DataFetchResult.getSubResult(result, r -> parseChartData(r.getMostRecentDaysActivity(), false)));
if (result != null
&& result.getResultType() == DataFetchResult.ResultType.SUCCESS
@@ -282,9 +293,41 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
super.close();
}
+ /**
+ * Create a default cell model to be use with excel export in the earliest /
+ * latest date format.
+ *
+ * @param date The date.
+ * @return The cell model.
+ */
+ 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(), true)))));
}
/**
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java
index 97fc63fc51..e11e638459 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java
@@ -47,8 +47,9 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableLabel;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.PieChartExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.PieChartPanel;
-import org.sleuthkit.autopsy.datasourcesummary.uiutils.PieChartPanel.PieChartItem;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.PieChartItem;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TskCoreException;
@@ -64,6 +65,7 @@ import org.sleuthkit.datamodel.TskCoreException;
"TypesPanel_filesByCategoryTable_slackRow_title=Slack Files",
"TypesPanel_filesByCategoryTable_directoryRow_title=Directories",
"TypesPanel_fileMimeTypesChart_title=File Types",
+ "TypesPanel_fileMimeTypesChart_valueLabel=Count",
"TypesPanel_fileMimeTypesChart_audio_title=Audio",
"TypesPanel_fileMimeTypesChart_documents_title=Documents",
"TypesPanel_fileMimeTypesChart_executables_title=Executables",
@@ -197,6 +199,8 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
private final DataFetcher osFetcher;
private final DataFetcher sizeFetcher;
+ private final DataFetcher typesFetcher;
+
private final DataFetcher allocatedFetcher;
private final DataFetcher unallocatedFetcher;
private final DataFetcher slackFetcher;
@@ -261,6 +265,8 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
this.osFetcher = containerData::getOperatingSystems;
this.sizeFetcher = (dataSource) -> dataSource == null ? null : dataSource.getSize();
+
+ this.typesFetcher = (dataSource) -> getMimeTypeCategoriesModel(mimeTypeData, dataSource);
this.allocatedFetcher = (dataSource) -> typeData.getCountOfAllocatedFiles(dataSource);
this.unallocatedFetcher = (dataSource) -> typeData.getCountOfUnallocatedFiles(dataSource);
@@ -274,9 +280,7 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
(sizeResult) -> sizeLabel.showDataFetchResult(
DataFetchResult.getSubResult(sizeResult,
size -> SizeRepresentationUtil.getSizeString(size, INTEGER_SIZE_FORMAT, false)))),
- new DataFetchWorker.DataFetchComponents<>(
- (dataSource) -> getMimeTypeCategoriesModel(mimeTypeData, dataSource),
- this::showMimeTypeCategories),
+ new DataFetchWorker.DataFetchComponents<>(typesFetcher, this::showMimeTypeCategories),
new DataFetchWorker.DataFetchComponents<>(allocatedFetcher,
countRes -> allocatedLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, (count) -> getStringOrZero(count)))),
new DataFetchWorker.DataFetchComponents<>(unallocatedFetcher,
@@ -442,6 +446,16 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
if (dataSource == null) {
return Collections.emptyList();
}
+
+ // Retrieve data to create the types pie chart
+ TypesPieChartData typesData = TypesPanel.getFetchResult(typesFetcher, "Types", dataSource);
+ PieChartExport typesChart = (typesData == null || !typesData.isUsefulContent()) ? null :
+ new PieChartExport(
+ Bundle.TypesPanel_fileMimeTypesChart_title(),
+ Bundle.TypesPanel_fileMimeTypesChart_valueLabel(),
+ "#,###",
+ Bundle.TypesPanel_fileMimeTypesChart_title(),
+ typesData.getPieSlices());
return Arrays.asList(new ExcelSpecialFormatExport(Bundle.TypesPanel_excelTabName(),
Stream.of(
@@ -449,6 +463,7 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
getStrExportable(osFetcher, Bundle.TypesPanel_osLabel_title(), dataSource),
new KeyValueItemExportable(Bundle.TypesPanel_sizeLabel_title(),
SizeRepresentationUtil.getBytesCell(getFetchResult(sizeFetcher, "Types", dataSource))),
+ typesChart,
getCountExportable(allocatedFetcher, Bundle.TypesPanel_filesByCategoryTable_allocatedRow_title(), dataSource),
getCountExportable(unallocatedFetcher, Bundle.TypesPanel_filesByCategoryTable_unallocatedRow_title(), dataSource),
getCountExportable(slackFetcher, Bundle.TypesPanel_filesByCategoryTable_slackRow_title(), dataSource),
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartExport.java
new file mode 100644
index 0000000000..8353549e75
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartExport.java
@@ -0,0 +1,273 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2021 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.datasourcesummary.uiutils;
+
+import java.awt.Color;
+import java.nio.ByteBuffer;
+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;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xddf.usermodel.XDDFColor;
+import org.apache.poi.xddf.usermodel.XDDFShapeProperties;
+import org.apache.poi.xddf.usermodel.XDDFSolidFillProperties;
+import org.apache.poi.xddf.usermodel.chart.AxisCrosses;
+import org.apache.poi.xddf.usermodel.chart.AxisPosition;
+import org.apache.poi.xddf.usermodel.chart.BarDirection;
+import org.apache.poi.xddf.usermodel.chart.BarGrouping;
+import org.apache.poi.xddf.usermodel.chart.ChartTypes;
+import org.apache.poi.xddf.usermodel.chart.LegendPosition;
+import org.apache.poi.xddf.usermodel.chart.XDDFBarChartData;
+import org.apache.poi.xddf.usermodel.chart.XDDFCategoryAxis;
+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.XDDFValueAxis;
+import org.apache.poi.xssf.usermodel.XSSFChart;
+import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
+import org.apache.poi.xssf.usermodel.XSSFDrawing;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelExportException;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.ExcelItemExportable;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.ItemDimensions;
+
+/**
+ * Class that creates an excel stacked bar chart along with data table.
+ */
+public class BarChartExport implements ExcelItemExportable, ExcelSheetExport {
+
+ /**
+ * Creates an excel table model to be written to an excel sheet and used as
+ * a datasource for the chart.
+ *
+ * @param categories The categories with their data.
+ * @param keyColumnHeader The header column name for the table descriptions
+ * (i.e. types: file types / artifact types).
+ * @param valueFormatString The excel format string to use for values.
+ * @return An excel table export to be used as the data source for the chart
+ * in the excel document.
+ */
+ private static ExcelTableExport>, ? extends ExcelCellModel> getTableModel(
+ List categories, String keyColumnHeader, String chartTitle) {
+
+ // get the row keys by finding the series with the largest set of bar items
+ // (they should all be equal, but just in case)
+ List extends Object> rowKeys = categories.stream()
+ .filter(cat -> cat != null && cat.getItems() != null)
+ .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 of (bar chart category index, bar chart item index) -> 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));
+
+ // Create rows of data to be displayed where each row is a tuple of the bar chart item
+ // key and the list of values in category order.
+ 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());
+
+ // Create the model for the category column
+ ColumnModel>, DefaultCellModel>> categoryColumn
+ = new ColumnModel<>(keyColumnHeader, (row) -> new DefaultCellModel<>(row.getKey()));
+
+ // create the models for each category of data to be displayed
+ Stream>, DefaultCellModel>>> dataColumns = IntStream.range(0, categories.size())
+ .mapToObj(idx -> new ColumnModel<>(
+ categories.get(idx).getKey().toString(),
+ (row) -> new DefaultCellModel<>(row.getValue().get(idx))));
+
+ // create table
+ return new ExcelTableExport>, DefaultCellModel>>(
+ chartTitle,
+ Stream.concat(Stream.of(categoryColumn), dataColumns)
+ .collect(Collectors.toList()),
+ values
+ );
+ }
+
+ private static final int DEFAULT_ROW_SIZE = 15;
+ private static final int DEFAULT_COL_SIZE = 10;
+ private static final int DEFAULT_ROW_PADDING = 1;
+ private static final int DEFAULT_COL_OFFSET = 1;
+
+ private final ExcelTableExport>, ? extends ExcelCellModel> tableExport;
+ private final int colOffset;
+ private final int rowPadding;
+ private final int colSize;
+ private final int rowSize;
+ private final String chartTitle;
+ private final String sheetName;
+ private final List categories;
+ private final String keyColumnHeader;
+
+ /**
+ * Main constructor that assumes some defaults (i.e. chart size follows
+ * defaults and sheet name is chart title).
+ *
+ * @param keyColumnHeader The header column name for the table descriptions
+ * (i.e. types: file types / artifact types).
+ * @param valueFormatString The excel format string to use for values.
+ * @param chartTitle The title for the chart.
+ * @param categories The categories along with data.
+ */
+ public BarChartExport(String keyColumnHeader,
+ String valueFormatString,
+ String chartTitle,
+ List categories) {
+ this(keyColumnHeader, valueFormatString, chartTitle, chartTitle, categories,
+ DEFAULT_COL_OFFSET, DEFAULT_ROW_PADDING, DEFAULT_COL_SIZE, DEFAULT_ROW_SIZE);
+ }
+
+ /**
+ * Main constructor.
+ *
+ * @param keyColumnHeader The header column name for the table descriptions
+ * (i.e. types: file types / artifact types).
+ * @param valueFormatString The excel format string to use for values.
+ * @param chartTitle The title for the chart.
+ * @param sheetName The sheet name if used as a sheet export.
+ * @param categories The categories along with data.
+ * @param colOffset The column spacing between the table and the chart.
+ * @param rowPadding The padding between this and data above or below (if
+ * used as an ExcelItemExportable).
+ * @param colSize The column size of the chart.
+ * @param rowSize The row size of the chart.
+ */
+ public BarChartExport(String keyColumnHeader, String valueFormatString,
+ String chartTitle, String sheetName,
+ List categories,
+ int colOffset, int rowPadding, int colSize, int rowSize) {
+
+ this.keyColumnHeader = keyColumnHeader;
+ this.tableExport = getTableModel(categories, keyColumnHeader, chartTitle);
+ this.colOffset = colOffset;
+ this.rowPadding = rowPadding;
+ this.colSize = colSize;
+ this.rowSize = rowSize;
+ this.chartTitle = chartTitle;
+ this.sheetName = sheetName;
+ this.categories = categories;
+ }
+
+ @Override
+ public String getSheetName() {
+ return sheetName;
+ }
+
+ @Override
+ public void renderSheet(Sheet sheet, ExcelExport.WorksheetEnv env) throws ExcelExport.ExcelExportException {
+ write(sheet, 0, 0, env);
+ }
+
+ @Override
+ public ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException {
+ 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 + categories.size() + 1 + colOffset;
+
+ //createAnchor has arguments of (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 + 1, rowStart + rowSize + 1);
+
+ XSSFChart chart = drawing.createChart(anchor);
+ chart.setTitleText(chartTitle);
+ 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);
+ bottomAxis.setTitle(keyColumnHeader);
+ XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
+ leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
+ leftAxis.setVisible(false);
+
+ XDDFBarChartData data = (XDDFBarChartData) chart.createData(ChartTypes.BAR, bottomAxis, leftAxis);
+ data.setBarGrouping(BarGrouping.STACKED);
+
+ XDDFDataSource headerSource = XDDFDataSourcesFactory.fromStringCellRange(xssfSheet,
+ new CellRangeAddress(tableDimensions.getRowStart() + 1, tableDimensions.getRowEnd(),
+ tableDimensions.getColStart(), tableDimensions.getColStart()));
+
+ data.setBarDirection(BarDirection.COL);
+
+ // set data for each series and set color if applicable
+ for (int i = 0; i < categories.size(); i++) {
+ XDDFChartData.Series series = data.addSeries(headerSource,
+ 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();
+ byte[] colorArrRGB = new byte[]{colorArrARGB[1], colorArrARGB[2], colorArrARGB[3]};
+ XDDFSolidFillProperties fill = new XDDFSolidFillProperties(XDDFColor.from(colorArrRGB));
+ XDDFShapeProperties properties = series.getShapeProperties();
+ if (properties == null) {
+ properties = new XDDFShapeProperties();
+ }
+ properties.setFillProperties(fill);
+ series.setShapeProperties(properties);
+ }
+ }
+
+ chart.plot(data);
+
+ return new ItemDimensions(rowStart, colStart, Math.max(tableDimensions.getRowEnd(), rowStart + rowSize) + rowPadding, chartColStart + colSize);
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartPanel.java
index 09f5ade505..21f3bca572 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartPanel.java
@@ -19,9 +19,7 @@
package org.sleuthkit.autopsy.datasourcesummary.uiutils;
import java.awt.BorderLayout;
-import java.awt.Color;
import java.awt.Font;
-import java.util.Collections;
import java.util.List;
import javax.swing.JLabel;
import org.apache.commons.collections4.CollectionUtils;
@@ -34,90 +32,12 @@ import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.StandardBarPainter;
import org.jfree.data.category.DefaultCategoryDataset;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.BarChartSeries.BarChartItem;
/**
* A bar chart panel.
*/
-public class BarChartPanel extends AbstractLoadableComponent> {
-
- /**
- * Represents a series in a bar chart where all items pertain to one
- * category.
- */
- public static class BarChartSeries {
-
- private final Comparable> key;
- private final Color color;
- private final List items;
-
- /**
- * Main constructor.
- *
- * @param key The key.
- * @param color The color for this series.
- * @param items The bars to be displayed for this series.
- */
- public BarChartSeries(Comparable> key, Color color, List items) {
- this.key = key;
- this.color = color;
- this.items = (items == null) ? Collections.emptyList() : Collections.unmodifiableList(items);
- }
-
- /**
- * @return The color for this series.
- */
- public Color getColor() {
- return color;
- }
-
- /**
- * @return The bars to be displayed for this series.
- */
- public List getItems() {
- return items;
- }
-
- /**
- * @return The key for this item.
- */
- public Comparable> getKey() {
- return key;
- }
- }
-
- /**
- * An individual bar to be displayed in the bar chart.
- */
- public static class BarChartItem {
-
- private final Comparable> key;
- private final double value;
-
- /**
- * Main constructor.
- *
- * @param key The key.
- * @param value The value for this item.
- */
- public BarChartItem(Comparable> key, double value) {
- this.key = key;
- this.value = value;
- }
-
- /**
- * @return The key for this item.
- */
- public Comparable> getKey() {
- return key;
- }
-
- /**
- * @return The value for this item.
- */
- public double getValue() {
- return value;
- }
- }
+public class BarChartPanel extends AbstractLoadableComponent> {
/**
* JFreeChart bar charts don't preserve the order of bars provided to the
@@ -285,12 +205,12 @@ public class BarChartPanel extends AbstractLoadableComponent data) {
+ protected void setResults(List data) {
this.dataset.clear();
if (CollectionUtils.isNotEmpty(data)) {
for (int s = 0; s < data.size(); s++) {
- BarChartPanel.BarChartSeries series = data.get(s);
+ BarChartSeries series = data.get(s);
if (series != null && CollectionUtils.isNotEmpty(series.getItems())) {
if (series.getColor() != null) {
this.plot.getRenderer().setSeriesPaint(s, series.getColor());
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartSeries.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartSeries.java
new file mode 100644
index 0000000000..c1626f34b2
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/BarChartSeries.java
@@ -0,0 +1,101 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2021 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.datasourcesummary.uiutils;
+
+import java.awt.Color;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents a series in a bar chart where all items pertain to one category.
+ */
+public class BarChartSeries {
+
+ /**
+ * An individual bar to be displayed in the bar chart.
+ */
+ public static class BarChartItem {
+
+ private final Comparable> key;
+ private final double value;
+
+ /**
+ * Main constructor.
+ *
+ * @param key The key.
+ * @param value The value for this item.
+ */
+ public BarChartItem(Comparable> key, double value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ /**
+ * @return The key for this item.
+ */
+ public Comparable> getKey() {
+ return key;
+ }
+
+ /**
+ * @return The value for this item.
+ */
+ public double getValue() {
+ return value;
+ }
+ }
+ private final Comparable> key;
+ private final Color color;
+ private final List items;
+
+ /**
+ * Main constructor.
+ *
+ * @param key The key.
+ * @param color The color for this series.
+ * @param items The bars to be displayed for this series.
+ */
+ public BarChartSeries(Comparable> key, Color color, List items) {
+ this.key = key;
+ this.color = color;
+ this.items = (items == null) ? Collections.emptyList() : Collections.unmodifiableList(items);
+ }
+
+ /**
+ * @return The color for this series.
+ */
+ public Color getColor() {
+ return color;
+ }
+
+ /**
+ * @return The bars to be displayed for this series.
+ */
+ public List getItems() {
+ return items;
+ }
+
+ /**
+ * @return The key for this item.
+ */
+ public Comparable> getKey() {
+ return key;
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java
index 5592344e2c..c79cb381aa 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java
@@ -81,6 +81,8 @@ public class ExcelTableExport implements ExcelSheet
sheet.autoSizeColumn(i);
}
+ // freeze header row
+ sheet.createFreezePane(0, 1);
}
@Override
@@ -119,10 +121,8 @@ public class ExcelTableExport implements ExcelSheet
cell.setCellValue(columns.get(i).getHeaderTitle());
cell.setCellStyle(worksheetEnv.getHeaderStyle());
}
- // freeze header row
- sheet.createFreezePane(0, 1);
- // Create Cell Style for each column (if one is needed)
+ // Create Cell Style for each column (if one is needed)
for (int rowNum = 0; rowNum < safeData.size(); rowNum++) {
T rowData = safeData.get(rowNum);
Row row = sheet.createRow(rowNum + rowStart + 1);
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/JTablePanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/JTablePanel.java
index c224567d9e..be814d23a9 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/JTablePanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/JTablePanel.java
@@ -351,6 +351,20 @@ public class JTablePanel extends AbstractLoadableComponent> {
this.keyFunction = keyFunction;
return this;
}
+
+ /**
+ * Returns the selected items or null if no item is selected.
+ * @return The selected items or null if no item is selected.
+ */
+ public List getSelectedItems() {
+ int selectedRow = this.table.getSelectedRow();
+ int count = this.table.getSelectedRowCount();
+ if (selectedRow < 0 || this.tableModel == null || selectedRow + count > this.tableModel.getDataRows().size()) {
+ return null;
+ } else {
+ return this.tableModel.getDataRows().subList(selectedRow, selectedRow + count);
+ }
+ }
@Override
protected synchronized void setResults(List data) {
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartExport.java
new file mode 100644
index 0000000000..4005a34ebc
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartExport.java
@@ -0,0 +1,195 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2021 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.datasourcesummary.uiutils;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xddf.usermodel.chart.LegendPosition;
+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.xssf.usermodel.XSSFChart;
+import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
+import org.apache.poi.xssf.usermodel.XSSFDrawing;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.openxmlformats.schemas.drawingml.x2006.chart.CTPieChart;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelExportException;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.ExcelItemExportable;
+import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.ItemDimensions;
+
+/**
+ *
+ * Class that creates an excel pie chart along with data table.
+ */
+public class PieChartExport implements ExcelItemExportable, ExcelSheetExport {
+
+ private static final int DEFAULT_ROW_SIZE = 20;
+ private static final int DEFAULT_COL_SIZE = 10;
+ private static final int DEFAULT_ROW_PADDING = 1;
+ private static final int DEFAULT_COL_OFFSET = 1;
+
+ private final ExcelTableExport tableExport;
+ private final int colOffset;
+ private final int rowPadding;
+ private final int colSize;
+ private final int rowSize;
+ private final String chartTitle;
+ private final String sheetName;
+
+ /**
+ * Main constructor assuming defaults.
+ *
+ * @param keyColumnHeader The header column name for the table descriptions
+ * (i.e. file types).
+ * @param valueColumnHeader The header column name for the values.
+ * @param valueFormatString The excel format string to use for values.
+ * @param chartTitle The title for the chart.
+ * @param slices The values for the pie slices.
+ */
+ public PieChartExport(String keyColumnHeader,
+ String valueColumnHeader, String valueFormatString,
+ String chartTitle,
+ List slices) {
+ this(keyColumnHeader, valueColumnHeader, valueFormatString, chartTitle, chartTitle, slices,
+ DEFAULT_COL_OFFSET, DEFAULT_ROW_PADDING, DEFAULT_COL_SIZE, DEFAULT_ROW_SIZE);
+ }
+
+ /**
+ * Main constructor.
+ *
+ * @param keyColumnHeader The header column name for the table descriptions
+ * (i.e. file types).
+ * @param valueColumnHeader The header column name for the values.
+ * @param valueFormatString The excel format string to use for values.
+ * @param chartTitle The title for the chart.
+ * @param sheetName The sheet name if used as a sheet export.
+ * @param slices The values for the pie slices.
+ * @param colOffset The column spacing between the table and the chart.
+ * @param rowPadding The padding between this and data above or below (if
+ * used as an ExcelItemExportable).
+ * @param colSize The column size of the chart.
+ * @param rowSize The row size of the chart.
+ */
+ public PieChartExport(String keyColumnHeader,
+ String valueColumnHeader, String valueFormatString,
+ String chartTitle, String sheetName,
+ List slices,
+ int colOffset, int rowPadding, int colSize, int rowSize) {
+
+ this.tableExport = new ExcelTableExport<>(chartTitle,
+ Arrays.asList(
+ new ColumnModel<>(keyColumnHeader, (slice) -> new DefaultCellModel<>(slice.getLabel())),
+ new ColumnModel<>(valueColumnHeader, (slice) -> new DefaultCellModel<>(slice.getValue(), null, valueFormatString))
+ ),
+ slices);
+ this.colOffset = colOffset;
+ this.rowPadding = rowPadding;
+ this.colSize = colSize;
+ this.rowSize = rowSize;
+ this.chartTitle = chartTitle;
+ this.sheetName = sheetName;
+ }
+
+ @Override
+ public String getSheetName() {
+ return sheetName;
+ }
+
+ @Override
+ public void renderSheet(Sheet sheet, ExcelExport.WorksheetEnv env) throws ExcelExport.ExcelExportException {
+ write(sheet, 0, 0, env);
+ }
+
+ @Override
+ public ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException {
+ 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;
+
+ //createAnchor has arguments of (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 + 1, rowStart + rowSize + 1);
+
+ XSSFChart chart = drawing.createChart(anchor);
+ chart.setTitleText(chartTitle);
+ chart.setTitleOverlay(false);
+ XDDFChartLegend legend = chart.getOrAddLegend();
+ legend.setPosition(LegendPosition.RIGHT);
+
+ // CellRangeAddress has arguments of (int firstRow, int lastRow, int firstCol, int lastCol)
+ XDDFDataSource cat = XDDFDataSourcesFactory.fromStringCellRange(xssfSheet,
+ new CellRangeAddress(tableDimensions.getRowStart() + 1, tableDimensions.getRowEnd(),
+ tableDimensions.getColStart(), tableDimensions.getColStart()));
+
+ XDDFNumericalDataSource val = XDDFDataSourcesFactory.fromNumericCellRange(xssfSheet,
+ new CellRangeAddress(tableDimensions.getRowStart() + 1, tableDimensions.getRowEnd(),
+ tableDimensions.getColStart() + 1, tableDimensions.getColStart() + 1));
+
+ // NOTE: There appears to be a classpath issue with POI (a version of 4.0.1 and 4.1.1 simultaneously)
+ // that is causing conflicts for XDDFPieChartData creation (i.e. the compiler thinks its using 4.1.1
+ // and the runtime thinks its using 4.0.1) Reflection is used below to use the 4.0.1 method while
+ // sidestepping compiler issues.
+ // XDDFPieChartData creation that can be used in poi >= 4.1.1:
+ // XDDFPieChartData data = (XDDFPieChartData) chart.createData(ChartTypes.PIE, bottomAxis, leftAxis);
+ // XDDFPieChartData creation that can be used in 4.0.1:
+ // XDDFPieChartData data = new XDDFPieChartData(chart.getCTChart().getPlotArea().addNewPieChart());
+ XDDFPieChartData data;
+ try {
+ Constructor constructor = XDDFPieChartData.class.getConstructor(CTPieChart.class);
+ constructor.setAccessible(true);
+ data = constructor.newInstance(chart.getCTChart().getPlotArea().addNewPieChart());
+ } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException | IllegalArgumentException ex) {
+ throw new ExcelExportException("Error while instantiating chart data.", ex);
+ }
+
+ data.setVaryColors(true);
+ data.addSeries(cat, val);
+
+ // Add data labels
+ if (!chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).isSetDLbls()) {
+ chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).addNewDLbls();
+ }
+
+ chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getDLbls().addNewShowVal().setVal(true);
+ chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getDLbls().addNewShowSerName().setVal(false);
+ chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getDLbls().addNewShowCatName().setVal(true);
+ chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getDLbls().addNewShowPercent().setVal(true);
+ chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getDLbls().addNewShowLegendKey().setVal(false);
+
+ chart.plot(data);
+
+ return new ItemDimensions(rowStart, colStart, Math.max(tableDimensions.getRowEnd(), rowStart + rowSize) + rowPadding, chartColStart + colSize);
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartItem.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartItem.java
new file mode 100644
index 0000000000..b00a97e996
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartItem.java
@@ -0,0 +1,67 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2021 Basis Technology Corp.
+ * Contact: carrier sleuthkit org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.datasourcesummary.uiutils;
+
+import java.awt.Color;
+
+/**
+ * An individual pie chart slice in the pie chart.
+ */
+public class PieChartItem {
+
+ private final String label;
+ private final double value;
+ private final Color color;
+
+ /**
+ * Main constructor.
+ *
+ * @param label The label for this pie slice.
+ * @param value The value for this item.
+ * @param color The color for the pie slice. Can be null for
+ * auto-determined.
+ */
+ public PieChartItem(String label, double value, Color color) {
+ this.label = label;
+ this.value = value;
+ this.color = color;
+ }
+
+ /**
+ * @return The label for this item.
+ */
+ public String getLabel() {
+ return label;
+ }
+
+ /**
+ * @return The value for this item.
+ */
+ public double getValue() {
+ return value;
+ }
+
+ /**
+ * @return The color for the pie slice or null for auto-determined.
+ */
+ public Color getColor() {
+ return color;
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartPanel.java
index fa0d00dab6..1e0ddde838 100644
--- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/PieChartPanel.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2019 Basis Technology Corp.
+ * Copyright 2020 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -39,52 +39,7 @@ import org.openide.util.NbBundle.Messages;
@Messages({
"PieChartPanel_noDataLabel=No Data"
})
-public class PieChartPanel extends AbstractLoadableComponent> {
-
- /**
- * An individual pie chart slice in the pie chart.
- */
- public static class PieChartItem {
-
- private final String label;
- private final double value;
- private final Color color;
-
- /**
- * Main constructor.
- *
- * @param label The label for this pie slice.
- * @param value The value for this item.
- * @param color The color for the pie slice. Can be null for
- * auto-determined.
- */
- public PieChartItem(String label, double value, Color color) {
- this.label = label;
- this.value = value;
- this.color = color;
- }
-
- /**
- * @return The label for this item.
- */
- public String getLabel() {
- return label;
- }
-
- /**
- * @return The value for this item.
- */
- public double getValue() {
- return value;
- }
-
- /**
- * @return The color for the pie slice or null for auto-determined.
- */
- public Color getColor() {
- return color;
- }
- }
+public class PieChartPanel extends AbstractLoadableComponent> {
private static final long serialVersionUID = 1L;
@@ -176,12 +131,12 @@ public class PieChartPanel extends AbstractLoadableComponent data) {
+ protected void setResults(List data) {
this.dataset.clear();
this.plot.clearSectionPaints(false);
if (data != null && !data.isEmpty()) {
- for (PieChartPanel.PieChartItem slice : data) {
+ for (PieChartItem slice : data) {
this.dataset.setValue(slice.getLabel(), slice.getValue());
if (slice.getColor() != null) {
this.plot.setSectionPaint(slice.getLabel(), slice.getColor());
@@ -202,7 +157,7 @@ public class PieChartPanel extends AbstractLoadableComponent data, String message) {
+ public synchronized void showDataWithMessage(List data, String message) {
setResults(data);
setMessage(true, message);
repaint();
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java
index c86f90ad4b..3b88aad79d 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java
@@ -85,9 +85,8 @@ public final class IngestProgressSnapshotDialog extends JDialog {
this.getRootPane().registerKeyboardAction(e -> {
this.dispose();
}, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
- add(new IngestProgressSnapshotPanel(this, provider));
+ add(new IngestProgressSnapshotPanel(this, provider), BorderLayout.CENTER);
pack();
- setResizable(false);
if (shouldBeModal) { // if called from a modal dialog, become modal, otherwise don't.
setModal(true);
}
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotPanel.form b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotPanel.form
index 5c0c3dbc16..5795cd77f6 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotPanel.form
@@ -1,6 +1,14 @@