27 package org.sleuthkit.autopsy.timeline.ui.detailview;
 
   29 import java.util.ArrayList;
 
   30 import java.util.Collections;
 
   31 import java.util.List;
 
   32 import javafx.beans.property.ObjectProperty;
 
   33 import javafx.beans.property.ObjectPropertyBase;
 
   34 import javafx.beans.property.ReadOnlyDoubleProperty;
 
   35 import javafx.beans.property.ReadOnlyDoubleWrapper;
 
   36 import javafx.scene.chart.Axis;
 
   37 import org.joda.time.DateTime;
 
   38 import org.joda.time.Interval;
 
   52 final class DateAxis 
extends Axis<DateTime> {
 
   54     private ObjectProperty<DateTime> lowerBound = 
new ObjectPropertyBase<DateTime>(
new DateTime(0)) {
 
   56         protected void invalidated() {
 
   57             if (!isAutoRanging()) {
 
   64         public Object getBean() {
 
   69         public String getName() {
 
   79     private DateTime maxDate;
 
   86     private DateTime minDate;
 
   88     private RangeDivision rangeDivisionInfo;
 
   90     private final ReadOnlyDoubleWrapper tickSpacing = 
new ReadOnlyDoubleWrapper();
 
   92     private final ObjectProperty<DateTime> upperBound = 
new ObjectPropertyBase<DateTime>(
new DateTime(1)) {
 
   94         protected void invalidated() {
 
   95             if (!isAutoRanging()) {
 
  102         public Object getBean() {
 
  103             return DateAxis.this;
 
  107         public String getName() {
 
  118         setAutoRanging(
false);
 
  119         setTickLabelsVisible(
false);
 
  121         setTickMarkVisible(
false);
 
  125     public double getDisplayPosition(DateTime date) {
 
  126         final double length = -200 + (getSide().isHorizontal() ? getWidth() : getHeight());
 
  129         double diff = getUpperBound().getMillis() - getLowerBound().getMillis();
 
  133         double range = length - getZeroPosition();
 
  137         double d = (date.getMillis() - getLowerBound().getMillis()) / diff;
 
  140         if (getSide().isVertical()) {
 
  141             return getHeight() - d * range + getZeroPosition();
 
  143             return d * range + getZeroPosition();
 
  154     public DateTime getLowerBound() {
 
  155         return lowerBound.get();
 
  165     public void setLowerBound(DateTime date) {
 
  166         lowerBound.set(date);
 
  176     public DateTime getUpperBound() {
 
  177         return upperBound.get();
 
  187     public void setUpperBound(DateTime date) {
 
  188         upperBound.set(date);
 
  192     public DateTime getValueForDisplay(
double displayPosition) {
 
  193         final double length = - 200 + (getSide().isHorizontal() ? getWidth() : getHeight());
 
  196         double diff = getUpperBound().getMillis() - getLowerBound().getMillis();
 
  200         double range = length - getZeroPosition();
 
  202         if (getSide().isVertical()) {
 
  205             return new DateTime((
long) ((displayPosition - getZeroPosition() - getHeight()) / -range * diff + getLowerBound().getMillis()), TimeLineController.getJodaTimeZone());
 
  209             return new DateTime((
long) ((displayPosition - getZeroPosition()) / range * diff + getLowerBound().getMillis()), TimeLineController.getJodaTimeZone());
 
  214     public double getZeroPosition() {
 
  219     public void invalidateRange(List<DateTime> list) {
 
  220         super.invalidateRange(list);
 
  222         Collections.sort(list);
 
  223         if (list.isEmpty()) {
 
  224             minDate = maxDate = 
new DateTime();
 
  225         } 
else if (list.size() == 1) {
 
  226             minDate = maxDate = list.get(0);
 
  227         } 
else if (list.size() > 1) {
 
  228             minDate = list.get(0);
 
  229             maxDate = list.get(list.size() - 1);
 
  234     public boolean isValueOnAxis(DateTime date) {
 
  235         return date.getMillis() > getLowerBound().getMillis() && date.getMillis() < getUpperBound().getMillis();
 
  239     public double toNumericValue(DateTime date) {
 
  240         return date.getMillis();
 
  244     public DateTime toRealValue(
double v) {
 
  245         return new DateTime((
long) v);
 
  249     protected Interval autoRange(
double length) {
 
  250         if (isAutoRanging()) {
 
  251             return new Interval(minDate, maxDate);
 
  253             if (getLowerBound() == null || getUpperBound() == null) {
 
  261     protected List<DateTime> calculateTickValues(
double length, Object range) {
 
  262         List<DateTime> tickDates = 
new ArrayList<>();
 
  266         rangeDivisionInfo = RangeDivision.getRangeDivision((Interval) range, TimeLineController.getJodaTimeZone());
 
  267         final DateTime lowerBound1 = getLowerBound();
 
  268         final DateTime upperBound1 = getUpperBound();
 
  270         if (lowerBound1 == null || upperBound1 == null) {
 
  273         DateTime lower = lowerBound1.withZone(TimeLineController.getJodaTimeZone());
 
  274         DateTime upper = upperBound1.withZone(TimeLineController.getJodaTimeZone());
 
  276         DateTime current = lower;
 
  278         while (current.isBefore(upper)) {
 
  279             tickDates.add(current);
 
  280             current = current.plus(rangeDivisionInfo.getPeriodSize().toUnitPeriod());
 
  284         tickDates.add(upper);
 
  290         if (tickDates.size() > 2) {
 
  291             DateTime secondDate = tickDates.get(1);
 
  292             DateTime thirdDate = tickDates.get(2);
 
  293             DateTime lastDate = tickDates.get(tickDates.size() - 2);
 
  294             DateTime previousLastDate = tickDates.get(tickDates.size() - 3);
 
  297             if (secondDate.getMillis() - lower.getMillis() < (thirdDate.getMillis() - secondDate.getMillis()) / 2) {
 
  298                 tickDates.remove(lower);
 
  303             if (upper.getMillis() - lastDate.getMillis() < (lastDate.getMillis() - previousLastDate.getMillis()) / 2) {
 
  304                 tickDates.remove(lastDate);
 
  308         if (tickDates.size() >= 2) {
 
  309             tickSpacing.set(getDisplayPosition(tickDates.get(1)) - getDisplayPosition(tickDates.get(0)));
 
  310         } 
else if (tickDates.size() >= 4) {
 
  311             tickSpacing.set(getDisplayPosition(tickDates.get(2)) - getDisplayPosition(tickDates.get(1)));
 
  317     protected Interval getRange() {
 
  318         return new Interval(getLowerBound(), getUpperBound());
 
  322     protected String getTickMarkLabel(DateTime date) {
 
  323         return rangeDivisionInfo.getTickFormatter().print(date);
 
  332     protected void setRange(Object range, 
boolean animating) {
 
  333         rangeDivisionInfo = RangeDivision.getRangeDivision((Interval) range, TimeLineController.getJodaTimeZone());
 
  334         setLowerBound(
new DateTime(rangeDivisionInfo.getLowerBound(), TimeLineController.getJodaTimeZone()));
 
  335         setUpperBound(
new DateTime(rangeDivisionInfo.getUpperBound(), TimeLineController.getJodaTimeZone()));
 
  338     ReadOnlyDoubleProperty getTickSpacing() {
 
  339         return tickSpacing.getReadOnlyProperty();