Autopsy 4.22.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
AbstractTimelineChart.java
Go to the documentation of this file.
1/*
2 * Autopsy Forensic Browser
3 *
4 * Copyright 2011-2018 Basis Technology Corp.
5 * Contact: carrier <at> sleuthkit <dot> org
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19package org.sleuthkit.autopsy.timeline.ui;
20
21import java.util.Comparator;
22import java.util.HashMap;
23import java.util.Map;
24import javafx.application.Platform;
25import javafx.beans.binding.DoubleBinding;
26import javafx.collections.FXCollections;
27import javafx.collections.ListChangeListener;
28import javafx.collections.ObservableList;
29import javafx.collections.transformation.SortedList;
30import javafx.geometry.Pos;
31import javafx.scene.Node;
32import javafx.scene.chart.Axis;
33import javafx.scene.chart.XYChart;
34import javafx.scene.control.Label;
35import javafx.scene.control.OverrunStyle;
36import javafx.scene.control.Tooltip;
37import javafx.scene.layout.Border;
38import javafx.scene.layout.BorderStroke;
39import javafx.scene.layout.BorderStrokeStyle;
40import javafx.scene.layout.BorderWidths;
41import javafx.scene.layout.CornerRadii;
42import javafx.scene.layout.HBox;
43import javafx.scene.layout.Pane;
44import javafx.scene.layout.Region;
45import javafx.scene.layout.VBox;
46import javafx.scene.paint.Color;
47import javafx.scene.text.Font;
48import javafx.scene.text.FontWeight;
49import javafx.scene.text.Text;
50import javafx.scene.text.TextAlignment;
51import javax.annotation.concurrent.Immutable;
52import org.apache.commons.lang3.StringUtils;
53import org.openide.util.NbBundle;
54import org.sleuthkit.autopsy.coreutils.Logger;
55import org.sleuthkit.autopsy.coreutils.ThreadConfined;
56import org.sleuthkit.autopsy.timeline.TimeLineController;
57import org.sleuthkit.datamodel.TimelineEventType;
58
73public abstract class AbstractTimelineChart<X, Y, NodeType extends Node, ChartType extends Region & TimeLineChart<X>> extends AbstractTimeLineView {
74
75 private static final Logger logger = Logger.getLogger(AbstractTimelineChart.class.getName());
76
77 @NbBundle.Messages("AbstractTimelineChart.defaultTooltip.text=Drag the mouse to select a time interval to zoom into.\nRight-click for more actions.")
78 private static final Tooltip DEFAULT_TOOLTIP = new Tooltip(Bundle.AbstractTimelineChart_defaultTooltip_text());
79 private static final Border ONLY_LEFT_BORDER = new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 0, 0, 1)));
80
87 static public Tooltip getDefaultTooltip() {
88 return DEFAULT_TOOLTIP;
89 }
90
97 protected ObservableList<NodeType> getSelectedNodes() {
98 return selectedNodes;
99 }
100
104 protected final ObservableList<XYChart.Series<X, Y>> dataSeries = FXCollections.<XYChart.Series<X, Y>>observableArrayList();
105 protected final Map<TimelineEventType, XYChart.Series<X, Y>> eventTypeToSeriesMap = new HashMap<>();
106
107 private ChartType chart;
108
110 private final Pane specificLabelPane = new Pane(); // container for the specfic labels in the decluttered axis
111 private final Pane contextLabelPane = new Pane();// container for the contextual labels in the decluttered axis
112// container for the contextual labels in the decluttered axis
113 private final Region spacer = new Region();
114
115 final private ObservableList<NodeType> selectedNodes = FXCollections.observableArrayList();
116
117 public Pane getSpecificLabelPane() {
118 return specificLabelPane;
119 }
120
121 public Pane getContextLabelPane() {
122 return contextLabelPane;
123 }
124
125 public Region getSpacer() {
126 return spacer;
127 }
128
134 protected ChartType getChart() {
135 return chart;
136 }
137
144 protected void setChart(ChartType chart) {
145 this.chart = chart;
146 setCenter(chart);
147 }
148
154 protected void applySelectionEffect(NodeType node) {
155 applySelectionEffect(node, true);
156 }
157
163 protected void removeSelectionEffect(NodeType node) {
164 applySelectionEffect(node, Boolean.FALSE);
165 }
166
176 abstract protected Boolean isTickBold(X value);
177
186 abstract protected void applySelectionEffect(NodeType node, Boolean applied);
187
195 abstract protected String getTickMarkLabel(X tickValue);
196
203 abstract protected double getTickSpacing();
204
210 abstract protected Axis<X> getXAxis();
211
217 abstract protected Axis<Y> getYAxis();
218
226 abstract protected double getAxisMargin();
227
231 protected final void createSeries() {
232 for (TimelineEventType eventType : getController().getEventsModel().getEventTypes()) {
233 XYChart.Series<X, Y> series = new XYChart.Series<>();
234 series.setName(eventType.getDisplayName());
235 eventTypeToSeriesMap.put(eventType, series);
236 dataSeries.add(series);
237 }
238 }
239
248 protected final XYChart.Series<X, Y> getSeries(final TimelineEventType eventType) {
249 return eventTypeToSeriesMap.get(eventType);
250 }
251
258 super(controller);
259 Platform.runLater(new Runnable() {
260 @Override
261 public void run() {
262 VBox vBox = new VBox(getSpecificLabelPane(), getContextLabelPane());
263 vBox.setFillWidth(false);
264 HBox hBox = new HBox(getSpacer(), vBox);
265 hBox.setFillHeight(false);
266 setBottom(hBox);
267 DoubleBinding spacerSize = getYAxis().widthProperty().add(getYAxis().tickLengthProperty()).add(getAxisMargin());
268 getSpacer().minWidthProperty().bind(spacerSize);
269 getSpacer().prefWidthProperty().bind(spacerSize);
270 getSpacer().maxWidthProperty().bind(spacerSize);
271 }
272 });
273
274 createSeries();
275
276 selectedNodes.addListener((ListChangeListener.Change<? extends NodeType> change) -> {
277 while (change.next()) {
278 change.getRemoved().forEach(node -> applySelectionEffect(node, false));
279 change.getAddedSubList().forEach(node -> applySelectionEffect(node, true));
280 }
281 });
282
283 //show tooltip text in status bar
284 hoverProperty().addListener(hoverProp -> controller.setStatusMessage(isHover() ? getDefaultTooltip().getText() : ""));
285
286 }
287
307 protected synchronized void layoutDateLabels() {
308 //clear old labels
309 contextLabelPane.getChildren().clear();
310 specificLabelPane.getChildren().clear();
311 //since the tickmarks aren't necessarily in value/position order,
312 //make a copy of the list sorted by position along axis
313 SortedList<Axis.TickMark<X>> tickMarks = getXAxis().getTickMarks().sorted(Comparator.comparing(Axis.TickMark::getPosition));
314
315 if (tickMarks.isEmpty()) {
316 /*
317 * Since StackedBarChart does some funky animation/background thread
318 * stuff, sometimes there are no tick marks even though there is
319 * data. Dispatching another call to layoutDateLables() allows that
320 * stuff time to run before we check a gain.
321 */
322 Platform.runLater(this::layoutDateLabels);
323 } else {
324 //get the spacing between ticks in the underlying axis
325 double spacing = getTickSpacing();
326
327 //initialize values from first tick
328 TwoPartDateTime dateTime = new TwoPartDateTime(getTickMarkLabel(tickMarks.get(0).getValue()));
329 String lastSeenContextLabel = dateTime.context;
330
331 //x-positions (pixels) of the current branch and leaf labels
332 double specificLabelX = 0;
333
334 if (dateTime.context.isEmpty()) {
335 //if there is only one part to the date (ie only year), just add a label for each tick
336 for (Axis.TickMark<X> t : tickMarks) {
338 spacing,
339 specificLabelX,
340 isTickBold(t.getValue())
341 );
342 specificLabelX += spacing; //increment x
343 }
344 } else {
345 //there are two parts so ...
346 //initialize additional state
347 double contextLabelX = 0;
348 double contextLabelWidth = 0;
349
350 for (Axis.TickMark<X> t : tickMarks) {
351 //split the label into a TwoPartDateTime
352 dateTime = new TwoPartDateTime(getTickMarkLabel(t.getValue()));
353
354 //if we are still in the same context
355 if (lastSeenContextLabel.equals(dateTime.context)) {
356 //increment context width
357 contextLabelWidth += spacing;
358 } else {// we are on to a new context, so ...
359 addContextLabel(lastSeenContextLabel, contextLabelWidth, contextLabelX);
360 //and then update label, x-pos, and width
361 lastSeenContextLabel = dateTime.context;
362 contextLabelX += contextLabelWidth;
363 contextLabelWidth = spacing;
364 }
365 //add the specific label (highest frequency part)
366 addSpecificLabel(dateTime.specifics, spacing, specificLabelX, isTickBold(t.getValue()));
367
368 //increment specific position
369 specificLabelX += spacing;
370 }
371 //we have reached end so add label for current context
372 addContextLabel(lastSeenContextLabel, contextLabelWidth, contextLabelX);
373 }
374 }
375 //request layout since we have modified scene graph structure
376 requestParentLayout();
377 }
378
390 private synchronized void addSpecificLabel(String labelText, double labelWidth, double labelX, boolean bold) {
391 Text label = new Text(" " + labelText + " "); //NON-NLS
392 label.setTextAlignment(TextAlignment.CENTER);
393 label.setFont(Font.font(null, bold ? FontWeight.BOLD : FontWeight.NORMAL, 10));
394 //position label accounting for width
395 label.relocate(labelX + labelWidth / 2 - label.getBoundsInLocal().getWidth() / 2, 0);
396 label.autosize();
397
398 if (specificLabelPane.getChildren().isEmpty()) {
399 //just add first label
400 specificLabelPane.getChildren().add(label);
401 } else {
402 //otherwise don't actually add the label if it would intersect with previous label
403
404 final Node lastLabel = specificLabelPane.getChildren().get(specificLabelPane.getChildren().size() - 1);
405
406 if (false == lastLabel.getBoundsInParent().intersects(label.getBoundsInParent())) {
407 specificLabelPane.getChildren().add(label);
408 }
409 }
410 }
411
421 private synchronized void addContextLabel(String labelText, double labelWidth, double labelX) {
422 Label label = new Label(labelText);
423 label.setAlignment(Pos.CENTER);
424 label.setTextAlignment(TextAlignment.CENTER);
425 label.setFont(Font.font(10));
426 //use a leading ellipse since that is the lowest frequency part,
427 //and can be infered more easily from other surrounding labels
428 label.setTextOverrun(OverrunStyle.LEADING_ELLIPSIS);
429 //force size
430 label.setMinWidth(labelWidth);
431 label.setPrefWidth(labelWidth);
432 label.setMaxWidth(labelWidth);
433 label.relocate(labelX, 0);
434
435 if (labelX == 0) { // first label has no border
436 label.setBorder(null);
437 } else { // subsequent labels have border on left to create dividers
438 label.setBorder(ONLY_LEFT_BORDER);
439 }
440
441 contextLabelPane.getChildren().add(label);
442 }
443
451 @Immutable
452 private static final class TwoPartDateTime {
453
457 private final String context;
458
462 private final String specifics;
463
470 TwoPartDateTime(String dateString) {
471 //find index of separator to split on
472 int splitIndex = StringUtils.lastIndexOfAny(dateString, " ", "-", ":"); //NON-NLS
473 if (splitIndex < 0) { // there is only one part
474 specifics = dateString;
475 context = ""; //NON-NLS
476 } else { //split at index
477 specifics = StringUtils.substring(dateString, splitIndex + 1);
478 context = StringUtils.substring(dateString, 0, splitIndex);
479 }
480 }
481 }
482
483}
synchronized static Logger getLogger(String name)
Definition Logger.java:124
final ObservableList< XYChart.Series< X, Y > > dataSeries
synchronized void addSpecificLabel(String labelText, double labelWidth, double labelX, boolean bold)
abstract void applySelectionEffect(NodeType node, Boolean applied)
synchronized void addContextLabel(String labelText, double labelWidth, double labelX)
final Map< TimelineEventType, XYChart.Series< X, Y > > eventTypeToSeriesMap
final XYChart.Series< X, Y > getSeries(final TimelineEventType eventType)

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.