19 package org.sleuthkit.autopsy.timeline.ui.countsview;
21 import java.util.ArrayList;
22 import java.util.Arrays;
24 import java.util.concurrent.ConcurrentHashMap;
25 import java.util.function.Function;
26 import javafx.application.Platform;
27 import javafx.beans.Observable;
28 import javafx.beans.property.SimpleObjectProperty;
29 import javafx.collections.FXCollections;
30 import javafx.concurrent.Task;
31 import javafx.event.ActionEvent;
32 import javafx.event.EventHandler;
33 import javafx.fxml.FXML;
34 import javafx.scene.Cursor;
35 import javafx.scene.Node;
36 import javafx.scene.chart.BarChart;
37 import javafx.scene.chart.CategoryAxis;
38 import javafx.scene.chart.NumberAxis;
39 import javafx.scene.chart.StackedBarChart;
40 import javafx.scene.chart.XYChart;
41 import javafx.scene.control.*;
42 import javafx.scene.effect.DropShadow;
43 import javafx.scene.effect.Effect;
44 import javafx.scene.effect.Lighting;
45 import javafx.scene.image.ImageView;
46 import javafx.scene.input.MouseButton;
47 import javafx.scene.input.MouseEvent;
48 import javafx.scene.layout.HBox;
49 import javafx.scene.layout.Pane;
50 import javafx.scene.layout.Region;
51 import javax.swing.JOptionPane;
52 import org.controlsfx.control.action.ActionGroup;
53 import org.controlsfx.control.action.ActionUtils;
54 import org.joda.time.DateTime;
55 import org.joda.time.Interval;
56 import org.joda.time.Seconds;
57 import org.openide.util.NbBundle;
102 private final CategoryAxis
dateAxis =
new CategoryAxis(FXCollections.<String>observableArrayList());
111 return labelValueString;
116 return dataSets.stream().flatMap((series) -> series.getData().stream())
117 .anyMatch((data) -> data.getXValue().equals(value) && data.getYValue().intValue() > 0);
122 ContextMenu chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(
new ActionGroup(
123 NbBundle.getMessage(
this.getClass(),
"Timeline.ui.countsview.contextMenu.ActionGroup.zoomHistory.title"),
125 chartContextMenu.setAutoHide(
true);
126 return chartContextMenu;
131 return new LoggedTask<Boolean>(NbBundle.getMessage(this.getClass(),
"CountsViewPane.loggedTask.name"),
true) {
134 protected Boolean call()
throws Exception {
138 updateProgress(-1, 1);
139 updateMessage(NbBundle.getMessage(
this.getClass(),
"CountsViewPane.loggedTask.prepUpdate"));
140 Platform.runLater(() -> {
141 setCursor(Cursor.WAIT);
145 chart.setRangeInfo(rangeInfo);
155 Platform.runLater(() -> {
156 updateMessage(NbBundle.getMessage(
this.getClass(),
"CountsViewPane.loggedTask.resetUI"));
157 eventTypeMap.clear();
159 dateAxis.getCategories().clear();
161 DateTime start = timeRange.getStart();
162 while (timeRange.contains(start)) {
165 dateAxis.getCategories().add(dateString);
175 DateTime start = timeRange.getStart();
176 while (timeRange.contains(start)) {
180 final Interval interval =
new Interval(start, end);
191 for (
final EventType et : eventCounts.keySet()) {
196 final Long count = eventCounts.get(et);
199 final double adjustedCount = count == 0 ? 0 : scale.get().adjust(count);
201 dateMax += adjustedCount;
202 final XYChart.Data<String, Number> xyData =
new BarChart.Data<>(dateString, adjustedCount);
204 xyData.nodeProperty().addListener((Observable o) -> {
205 final Node node = xyData.getNode();
208 node.setCursor(Cursor.HAND);
210 node.setOnMouseEntered((MouseEvent event) -> {
212 final Tooltip tooltip =
new Tooltip(
213 NbBundle.getMessage(
this.getClass(),
"CountsViewPane.tooltip.text",
217 interval.getEnd().toString(
219 tooltip.setGraphic(
new ImageView(et.getFXImage()));
220 Tooltip.install(node, tooltip);
221 node.setEffect(
new DropShadow(10, et.getColor()));
223 node.setOnMouseExited((MouseEvent event) -> {
225 node.setEffect(SELECTED_NODE_EFFECT);
227 node.setEffect(null);
231 node.addEventHandler(MouseEvent.MOUSE_CLICKED,
new BarClickHandler(node, dateString, interval, et));
235 max = Math.max(max, dateMax);
237 final double fmax = max;
239 Platform.runLater(() -> {
241 NbBundle.getMessage(
this.getClass(),
"CountsViewPane.loggedTask.updatingCounts"));
244 countAxis.setTickUnit(Math.pow(10, Math.max(0, Math.floor(Math.log10(fmax)) - 1)));
246 countAxis.setTickUnit(Double.MAX_VALUE);
248 countAxis.setUpperBound(1 + fmax * 1.2);
253 final double fmax = max;
255 Platform.runLater(() -> {
257 NbBundle.getMessage(
this.getClass(),
"CountsViewPane.loggedTask.updatingCounts"));
264 Platform.runLater(() -> {
265 updateMessage(NbBundle.getMessage(
this.getClass(),
"CountsViewPane.loggedTask.wrappingUp"));
266 updateProgress(1, 1);
268 setCursor(Cursor.NONE);
277 super(partPane, contextPane, spacer);
278 chart =
new EventCountsChart(dateAxis, countAxis);
285 dateAxis.getTickMarks().addListener((Observable observable) -> {
288 dateAxis.categorySpacingProperty().addListener((Observable observable) -> {
291 dateAxis.getCategories().addListener((Observable observable) -> {
295 spacer.minWidthProperty().bind(countAxis.widthProperty().add(countAxis.tickLengthProperty()).add(dateAxis.startMarginProperty().multiply(2)));
296 spacer.prefWidthProperty().bind(countAxis.widthProperty().add(countAxis.tickLengthProperty()).add(dateAxis.startMarginProperty().multiply(2)));
297 spacer.maxWidthProperty().bind(countAxis.widthProperty().add(countAxis.tickLengthProperty()).add(dateAxis.startMarginProperty().multiply(2)));
299 scale.addListener(o -> {
300 countAxis.tickLabelsVisibleProperty().bind(scale.isEqualTo(
ScaleType.
LINEAR));
301 countAxis.tickMarkVisibleProperty().bind(scale.isEqualTo(
ScaleType.
LINEAR));
302 countAxis.minorTickVisibleProperty().bind(scale.isEqualTo(
ScaleType.
LINEAR));
319 return dateAxis.getCategorySpacing();
345 private XYChart.Series<String, Number>
getSeries(
final EventType et) {
346 XYChart.Series<String, Number> series = eventTypeMap.get(et);
347 if (series == null) {
348 series =
new XYChart.Series<>();
350 eventTypeMap.put(et, series);
380 public BarClickHandler(Node node, String dateString, Interval countInterval, EventType type) {
381 this.interval = countInterval;
384 this.startDateString = dateString;
390 if (e.getClickCount() == 1) {
391 if (e.getButton().equals(MouseButton.PRIMARY)) {
394 }
else if (e.getButton().equals(MouseButton.SECONDARY)) {
395 Platform.runLater(() -> {
396 chart.getContextMenu().hide();
398 if (barContextMenu == null) {
399 barContextMenu =
new ContextMenu();
400 barContextMenu.setAutoHide(
true);
401 barContextMenu.getItems().addAll(
402 new MenuItem(NbBundle.getMessage(
this.getClass(),
403 "Timeline.ui.countsview.menuItem.selectTimeRange")) {
405 setOnAction((ActionEvent t) -> {
409 for (XYChart.Series<String, Number> s :
dataSets) {
410 s.getData().forEach((XYChart.Data<String, Number> d) -> {
411 if (startDateString.contains(d.getXValue())) {
412 selectedNodes.add(d.getNode());
419 new MenuItem(NbBundle.getMessage(
this.getClass(),
420 "Timeline.ui.countsview.menuItem.selectEventType")) {
422 setOnAction((ActionEvent t) -> {
426 eventTypeMap.get(type).getData().forEach((d) -> {
433 new MenuItem(NbBundle.getMessage(
this.getClass(),
434 "Timeline.ui.countsview.menuItem.selectTimeandType")) {
436 setOnAction((ActionEvent t) -> {
442 new SeparatorMenuItem(),
443 new MenuItem(NbBundle.getMessage(
this.getClass(),
444 "Timeline.ui.countsview.menuItem.zoomIntoTimeRange")) {
446 setOnAction((ActionEvent t) -> {
447 if (interval.toDuration().isShorterThan(Seconds.ONE.toStandardDuration()) ==
false) {
456 barContextMenu.show(node, e.getScreenX(), e.getScreenY());
460 }
else if (e.getClickCount() >= 2) {
461 if (interval.toDuration().isLongerThan(Seconds.ONE.toStandardDuration())) {
465 int showConfirmDialog = JOptionPane.showConfirmDialog(null,
466 NbBundle.getMessage(
CountsViewPane.class,
"CountsViewPane.detailSwitchMessage"),
467 NbBundle.getMessage(
CountsViewPane.class,
"CountsViewPane.detailSwitchTitle"), JOptionPane.YES_NO_OPTION);
468 if (showConfirmDialog == JOptionPane.YES_OPTION) {
513 assert logRadio != null :
"fx:id=\"logRadio\" was not injected: check your FXML file 'CountsViewSettingsPane.fxml'.";
514 assert linearRadio != null :
"fx:id=\"linearRadio\" was not injected: check your FXML file 'CountsViewSettingsPane.fxml'.";
515 logRadio.setSelected(
true);
516 scaleGroup.selectedToggleProperty().addListener(observable -> {
517 if (scaleGroup.getSelectedToggle() == linearRadio) {
520 if (scaleGroup.getSelectedToggle() == logRadio) {
525 logRadio.setText(NbBundle.getMessage(
this.getClass(),
"CountsViewPane.logRadio.text"));
526 linearRadio.setText(NbBundle.getMessage(
this.getClass(),
"CountsViewPane.linearRadio.text"));
527 scaleLabel.setText(NbBundle.getMessage(
this.getClass(),
"CountsViewPane.scaleLabel.text"));
540 private final Function<Long, Double>
func;
547 return func.apply(c);
static String getRGBCode(Color color)
void setChartClickHandler()
FilteredEventsModel filteredEvents
final Function< Long, Double > func
final Map< EventType, XYChart.Series< String, Number > > eventTypeMap
Task< Boolean > getUpdateTask()
final CategoryAxis dateAxis
static final Logger LOGGER
ReadOnlyListWrapper< N > selectedNodes
final NumberAxis countAxis
CountsViewPane(Pane partPane, Pane contextPane, Region spacer)
final SimpleObjectProperty< ScaleType > scale
void selectTimeAndType(Interval interval, EventType type)
BarClickHandler(Node node, String dateString, Interval countInterval, EventType type)
DateTimeFormatter getTickFormatter()
TimeLineController controller
ContextMenu getContextMenu()
final ObservableList< BarChart.Series< X, Y > > dataSets
static void construct(Node n, String fxmlFileName)
static RangeDivisionInfo getRangeDivisionInfo(Interval timeRange)
synchronized ReadOnlyObjectProperty< Interval > timeRange()
static RootEventType getInstance()
Effect getSelectionEffect()
Boolean isTickBold(String value)
String getTickMarkLabel(String labelValueString)
final String startDateString
Map< EventType, Long > getEventCounts(Interval timeRange)
final Interval getSpanningInterval()
static DateTimeZone getJodaTimeZone()
static final Effect SELECTED_NODE_EFFECT
synchronized void layoutDateLabels()
ScaleType(Function< Long, Double > func)
XYChart.Series< String, Number > getSeries(final EventType et)
void handle(final MouseEvent e)
TimeUnits getPeriodSize()
void applySelectionEffect(Node c1, Boolean applied)
synchronized void pushTimeRange(Interval timeRange)
ContextMenu barContextMenu
synchronized void setViewMode(VisualizationMode visualizationMode)
static Logger getLogger(String name)
List< Node > settingsNodes
synchronized void update()