diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index a02917d75f..bbc213de10 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -676,9 +676,6 @@ public class TimeLineController { } } - @NbBundle.Messages({"# {0} - the number of events", - "Timeline.pushDescrLOD.confdlg.msg=You are about to show details for {0} events. This might be very slow or even crash Autopsy.\n\nDo you want to continue?", - "Timeline.pushDescrLOD.confdlg.title=Change description level of detail?"}) synchronized public void pushDescrLOD(DescriptionLoD newLOD) { ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get(); if (currentZoom == null) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java index f685b8262e..fb645bdcd3 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.timeline.ui; +import com.google.common.eventbus.Subscribe; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -68,6 +69,7 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; +import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent; /** * Abstract base class for TimeLineChart based visualizations. @@ -410,6 +412,17 @@ public abstract class AbstractVisualizationPane extends BorderPane { IntervalSelector newIntervalSelector(); /** - * clear any references to previous interval selectors , including - * removing the interval selector from the ui / scene-graph + * Clear any references to previous interval selectors , including + * removing the interval selector from the UI / scene-graph. */ void clearIntervalSelector(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineChart.java index 22516eba78..ed73a29f45 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineChart.java @@ -44,8 +44,10 @@ public interface TimeLineChart extends ContextMenuProvider, IntervalSelectorP ObservableList getSelectedNodes(); + @Override IntervalSelector getIntervalSelector(); + @Override void setIntervalSelector(IntervalSelector newIntervalSelector); /** @@ -54,30 +56,29 @@ public interface TimeLineChart extends ContextMenuProvider, IntervalSelectorP * * @return a new interval selector */ + @Override IntervalSelector newIntervalSelector(); - /** - * clear any references to previous interval selectors , including removing - * the interval selector from the ui / scene-graph - */ + @Override void clearIntervalSelector(); + @Override public Axis getXAxis(); + @Override public TimeLineController getController(); /** - * drag handler class used by {@link TimeLineChart}s to create - * {@link IntervalSelector}s + * Drag handler class used by TimeLineCharts to create IntervalSelectors * - * @param the type of values along the horizontal axis - * @param the type of chart this is a drag handler for + * @param The type of values along the horizontal axis. + * @param The type of chart this is a drag handler for. */ public static class ChartDragHandler> implements EventHandler { private final Y chart; - private double startX; //hanlder mainstains position of drag start + private double startX; //hanlder maintains position of drag start public ChartDragHandler(Y chart) { this.chart = chart; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java index 49460d1ad8..7cb033f9a8 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java @@ -288,7 +288,7 @@ final public class VisualizationPanel extends BorderPane { } else if (newValue == detailsToggle && oldValue != null) { controller.setVisualizationMode(VisualizationMode.DETAIL); } else if (newValue == listToggle && oldValue != null) { - controller.setViewMode(VisualizationMode.LIST); + controller.setVisualizationMode(VisualizationMode.LIST); } }; @@ -387,8 +387,8 @@ final public class VisualizationPanel extends BorderPane { } /** - * Handle a RefreshRequestedEvent from the events model by refreshing the - * visualization. + * Handle a RefreshRequestedEvent from the events model by clearing the + * refresh notification. * * NOTE: This VisualizationPanel must be registered with the * filteredEventsModel's EventBus in order for this handler to be invoked. @@ -397,7 +397,6 @@ final public class VisualizationPanel extends BorderPane { */ @Subscribe public void handleRefreshRequested(RefreshRequestedEvent event) { - visualization.refresh(); Platform.runLater(() -> { if (Bundle.VisualizationPanel_tagsAddedOrDeleted().equals(notificationPane.getText())) { notificationPane.hide(); @@ -548,22 +547,18 @@ final public class VisualizationPanel extends BorderPane { controller.monitorTask(histogramTask); } + /** + * Refresh the time selection UI to match the current zoome paramaters. + */ private void refreshTimeUI() { - refreshTimeUI(filteredEvents.timeRangeProperty().get()); - } - - private void refreshTimeUI(Interval interval) { - RangeDivisionInfo rangeDivisionInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.getSpanningInterval()); - final long minTime = rangeDivisionInfo.getLowerBound(); final long maxTime = rangeDivisionInfo.getUpperBound(); - long startMillis = interval.getStartMillis(); - long endMillis = interval.getEndMillis(); + long startMillis = filteredEvents.getTimeRange().getStartMillis(); + long endMillis = filteredEvents.getTimeRange().getEndMillis(); if (minTime > 0 && maxTime > minTime) { - Platform.runLater(() -> { startPicker.localDateTimeProperty().removeListener(startListener); endPicker.localDateTimeProperty().removeListener(endListener); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/CountsViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/CountsViewPane.java index ead57e88bb..575a6ca900 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/CountsViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/countsview/CountsViewPane.java @@ -40,6 +40,7 @@ import javafx.scene.control.Label; import javafx.scene.control.RadioButton; import javafx.scene.control.ToggleGroup; import javafx.scene.control.Tooltip; +import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; @@ -60,7 +61,6 @@ import org.sleuthkit.autopsy.timeline.TimeLineController; import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel; import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane; -import static org.sleuthkit.autopsy.timeline.ui.countsview.Bundle.*; import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo; /** @@ -95,8 +95,8 @@ public class CountsViewPane extends AbstractVisualizationPane series.getData().stream()) - .anyMatch((data) -> data.getXValue().equals(value) && data.getYValue().intValue() > 0); + return dataSeries.stream().flatMap(series -> series.getData().stream()) + .anyMatch(data -> data.getXValue().equals(value) && data.getYValue().intValue() > 0); } @Override @@ -107,11 +107,7 @@ public class CountsViewPane extends AbstractVisualizationPane { - if (scaleGroup.getSelectedToggle() == linearRadio) { + scaleGroup.selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> { + if (newToggle == linearRadio) { scaleProp.set(Scale.LINEAR); - } else if (scaleGroup.getSelectedToggle() == logRadio) { + } else if (newToggle == logRadio) { scaleProp.set(Scale.LOGARITHMIC); } }); logRadio.setSelected(true); - //make a popup hrlp window with descriptions of the scales. - helpImageView.setCursor(Cursor.HAND); - helpImageView.setOnMouseClicked(clicked -> { - Text text = new Text(Bundle.CountsViewPane_scaleHelp()); - Text text2 = new Text(Bundle.CountsViewPane_scaleHelp2()); + //make a popup help "window" with a description of the log scale. + logImageView.setCursor(Cursor.HAND); + logImageView.setOnMouseClicked(clicked -> { + Text text = new Text(Bundle.CountsViewPane_scaleHelpLog()); + Text text2 = new Text(Bundle.CountsViewPane_scaleHelpLog2()); Font baseFont = text.getFont(); text2.setFont(Font.font(baseFont.getFamily(), FontWeight.BOLD, FontPosture.ITALIC, baseFont.getSize())); - Text text3 = new Text(Bundle.CountsViewPane_scaleHelp3()); + Text text3 = new Text(Bundle.CountsViewPane_scaleHelpLog3()); + showPopoverHelp(logImageView, + Bundle.CountsViewPane_logRadio_text(), + logImageView.getImage(), + new TextFlow(text, text2, text3)); + }); - Pane borderPane = new BorderPane(null, null, new ImageView(helpImageView.getImage()), - new TextFlow(text, text2, text3), - new Label(Bundle.CountsViewPane_scaleHelp_label_text())); - borderPane.setPadding(new Insets(10)); - borderPane.setPrefWidth(500); - - PopOver popOver = new PopOver(borderPane); - popOver.setDetachable(false); - popOver.setArrowLocation(PopOver.ArrowLocation.TOP_CENTER); - popOver.show(helpImageView); + //make a popup help "window" with a description of the linear scale. + linearImageView.setCursor(Cursor.HAND); + linearImageView.setOnMouseClicked(clicked -> { + Text text = new Text(Bundle.CountsViewPane_scaleHelpLinear()); + text.setWrappingWidth(480); //This is a hack to fix the layout. + showPopoverHelp(linearImageView, + Bundle.CountsViewPane_linearRadio_text(), + linearImageView.getImage(), text); }); } @@ -312,6 +314,33 @@ public class CountsViewPane extends AbstractVisualizationPane - + - + - - - + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java index 4f90a06bcd..13d76c6977 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/detailview/DetailViewPane.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.timeline.ui.detailview; import java.util.List; +import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -58,6 +59,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent; import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane; import org.sleuthkit.autopsy.timeline.utils.MappedList; import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD; +import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; /** * Controller class for a DetailsChart based implementation of a timeline view. @@ -89,6 +91,12 @@ public class DetailViewPane extends AbstractVisualizationPane> selectedEvents; + /** + * Local copy of the zoomParams. Used to backout of a zoomParam change + * without needing to requery/redraw the vis. + */ + private ZoomParams currentZoomParams; + /** * Constructor for a DetailViewPane * @@ -357,7 +365,8 @@ public class DetailViewPane extends AbstractVisualizationPane { DetailsUpdateTask() { @@ -367,13 +376,17 @@ public class DetailViewPane extends AbstractVisualizationPane { private final Axis verticalAxis; /** - * property that holds the interval selector if one is active + * Property that holds the interval selector if one is active */ - private final SimpleObjectProperty> intervalSelector = new SimpleObjectProperty<>(); + private final SimpleObjectProperty> intervalSelectorProp = new SimpleObjectProperty<>(); + + /** + * ObservableSet of GuieLines displayed in this chart + */ + private final ObservableSet guideLines = FXCollections.observableSet(); /** * Predicate used to determine if a EventNode should be highlighted. Can be @@ -74,12 +79,12 @@ final class DetailsChart extends Control implements TimeLineChart { private final SimpleObjectProperty>> highlightPredicate = new SimpleObjectProperty<>((x) -> false); /** - * an ObservableList of the Nodes that are selected in this chart. + * An ObservableList of the Nodes that are selected in this chart. */ private final ObservableList> selectedNodes; /** - * an ObservableList representing all the events in the tree as a flat list + * An ObservableList representing all the events in the tree as a flat list * of events whose roots are in the eventStripes lists * */ @@ -97,12 +102,25 @@ final class DetailsChart extends Control implements TimeLineChart { private final TimeLineController controller; /** - * an ObservableList of root event stripes to display in the chart. Must - * only be modified on the JFX Thread. + * An ObservableList of root event stripes to display in the chart. */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private final ObservableList rootEventStripes = FXCollections.observableArrayList(); + /** + * Constructor + * + * @param controller The TimeLineController for this chart. + * @param detailsChartDateAxis The DateAxis to use in this chart. + * @param pinnedDateAxis The DateAxis to use for the pinned lane. It + * will not be shown on screen, but must not be + * null or the same as the detailsChartDateAxis. + * @param verticalAxis An Axis to use as the vertical + * axis in the primary lane. + * @param selectedNodes An ObservableList>, that + * will be used to keep track of the nodes + * selected in this chart. + */ DetailsChart(TimeLineController controller, DateAxis detailsChartDateAxis, DateAxis pinnedDateAxis, Axis verticalAxis, ObservableList> selectedNodes) { this.controller = controller; this.layoutSettings = new DetailsChartLayoutSettings(controller); @@ -112,8 +130,9 @@ final class DetailsChart extends Control implements TimeLineChart { this.selectedNodes = selectedNodes; FilteredEventsModel eventsModel = getController().getEventsModel(); + /* - * if the time range is changed, clear the guide line and the interval + * If the time range is changed, clear the guide line and the interval * selector, since they may not be in view any more. */ eventsModel.timeRangeProperty().addListener(o -> clearTimeBasedUIElements()); @@ -126,9 +145,9 @@ final class DetailsChart extends Control implements TimeLineChart { * Get the DateTime represented by the given x-position in this chart. * * - * @param xPos the x-position to get the DataTime for + * @param xPos The x-position to get the DataTime for. * - * @return the DateTime represented by the given x-position in this chart. + * @return The DateTime represented by the given x-position in this chart. */ DateTime getDateTimeForPosition(double xPos) { return getXAxis().getValueForDisplay(getXAxis().parentToLocal(xPos, 0).getX()); @@ -137,7 +156,7 @@ final class DetailsChart extends Control implements TimeLineChart { /** * Add an EventStripe to the list of root stripes. * - * @param stripe the EventStripe to add. + * @param stripe The EventStripe to add. */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) void addStripe(EventStripe stripe) { @@ -146,10 +165,9 @@ final class DetailsChart extends Control implements TimeLineChart { } /** - * * Remove the given GuideLine from this chart. * - * @param guideLine the GuideLine to remove + * @param guideLine The GuideLine to remove. */ void clearGuideLine(GuideLine guideLine) { guideLines.remove(guideLine); @@ -161,23 +179,22 @@ final class DetailsChart extends Control implements TimeLineChart { } /** - * Get the DetailsCharLayoutSettings for this chart. + * Get the DetailsChartLayoutSettings for this chart. * - * @return the DetailsCharLayoutSettings for this chart. + * @return The DetailsChartLayoutSettings for this chart. */ DetailsChartLayoutSettings getLayoutSettings() { return layoutSettings; } /** - * * Set the Predicate used to determine if a EventNode should be highlighted. * Can be a combination of conditions such as: be in the selectedNodes list * OR have a particular description, but it must include be in the * selectedNodes (selectedNodes::contains). * - * @param highlightPredicate the Predicate used to determine which nodes to - * highlight + * @param highlightPredicate The Predicate used to determine which nodes to + * highlight. */ void setHighlightPredicate(Predicate> highlightPredicate) { this.highlightPredicate.set(highlightPredicate); @@ -208,14 +225,9 @@ final class DetailsChart extends Control implements TimeLineChart { clearIntervalSelector(); } - /** - * ObservableSet of GuieLines displayed in this chart - */ - private final ObservableSet guideLines = FXCollections.observableSet(); - @Override public void clearIntervalSelector() { - intervalSelector.set(null); + intervalSelectorProp.set(null); } @Override @@ -225,16 +237,12 @@ final class DetailsChart extends Control implements TimeLineChart { @Override public IntervalSelector getIntervalSelector() { - return intervalSelector.get(); - } - - private SimpleObjectProperty> intervalSelector() { - return intervalSelector; + return intervalSelectorProp.get(); } @Override public void setIntervalSelector(IntervalSelector newIntervalSelector) { - intervalSelector.set(newIntervalSelector); + intervalSelectorProp.set(newIntervalSelector); } @Override @@ -277,7 +285,7 @@ final class DetailsChart extends Control implements TimeLineChart { /** * Get the ObservableList of root EventStripes. * - * @return the ObservableList of root EventStripes. + * @return The ObservableList of root EventStripes. */ ObservableList getRootEventStripes() { return rootEventStripes; @@ -385,8 +393,9 @@ final class DetailsChart extends Control implements TimeLineChart { private final Pane rootPane; /** - * The divder position of masterDetailPane is saved when the pinned lane - * is hidden so it can be restored when the pinned lane is shown again. + * The divider position of masterDetailPane is saved when the pinned + * lane is hidden so it can be restored when the pinned lane is shown + * again. */ private double dividerPosition = .1; @@ -400,8 +409,7 @@ final class DetailsChart extends Control implements TimeLineChart { pinnedView = new ScrollingLaneWrapper(pinnedLane); pinnedLane.setMinHeight(MIN_PINNED_LANE_HEIGHT); - pinnedLane.maxVScrollProperty().addListener(maxVSCrollProp -> syncPinnedHeight()); - syncPinnedHeight(); + pinnedLane.maxVScrollProperty().addListener(maxVScroll -> syncPinnedHeight()); //assemble scene graph masterDetailPane = new MasterDetailPane(Side.TOP, primaryView, pinnedView, false); @@ -413,10 +421,8 @@ final class DetailsChart extends Control implements TimeLineChart { //maintain highlighted effect on correct nodes getSkinnable().highlightPredicate.addListener((observable, oldPredicate, newPredicate) -> { - primaryLane.getAllNodes().forEach(eNode -> - eNode.applyHighlightEffect(newPredicate.test(eNode))); - pinnedLane.getAllNodes().forEach(eNode -> - eNode.applyHighlightEffect(newPredicate.test(eNode))); + primaryLane.getAllNodes().forEach(primaryNode -> primaryNode.applyHighlightEffect(newPredicate.test(primaryNode))); + pinnedLane.getAllNodes().forEach(pinnedNode -> pinnedNode.applyHighlightEffect(newPredicate.test(pinnedNode))); }); //configure mouse listeners @@ -430,7 +436,7 @@ final class DetailsChart extends Control implements TimeLineChart { syncPinnedLaneShowing(); //show and remove interval selector in sync with control state change - getSkinnable().intervalSelector().addListener((observable, oldIntervalSelector, newIntervalSelector) -> { + getSkinnable().intervalSelectorProp.addListener((observable, oldIntervalSelector, newIntervalSelector) -> { rootPane.getChildren().remove(oldIntervalSelector); if (null != newIntervalSelector) { rootPane.getChildren().add(newIntervalSelector); @@ -460,11 +466,11 @@ final class DetailsChart extends Control implements TimeLineChart { /** * Add the given listeners to the given chart lane * - * @param chartLane the Chart lane to add the listeners to - * @param mouseClickedHandler the mouseClickedHandler to add to chart - * @param chartDragHandler1 the ChartDragHandler to add to the chart + * @param chartLane The Chart lane to add the listeners to. + * @param mouseClickedHandler The MouseClickedHandler to add to chart. + * @param chartDragHandler1 The ChartDragHandler to add to the chart * as pressed, released, dragged, and clicked - * handler + * handler. */ static private void configureMouseListeners(final DetailsChartLane chartLane, final TimeLineChart.MouseClickedHandler mouseClickedHandler, final TimeLineChart.ChartDragHandler chartDragHandler) { chartLane.setOnMousePressed(chartDragHandler); @@ -474,14 +480,22 @@ final class DetailsChart extends Control implements TimeLineChart { chartLane.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickedHandler); } + /** + * Show the pinned lane if and only if the settings object says it + * should be. + */ private void syncPinnedLaneShowing() { - boolean selected = getSkinnable().getLayoutSettings().isPinnedLaneShowing(); - if (selected == false) { + boolean pinnedLaneShowing = getSkinnable().getLayoutSettings().isPinnedLaneShowing(); + if (pinnedLaneShowing == false) { + //Save the divider position for later. dividerPosition = masterDetailPane.getDividerPosition(); } - masterDetailPane.setShowDetailNode(selected); - if (selected) { + + masterDetailPane.setShowDetailNode(pinnedLaneShowing); + + if (pinnedLaneShowing) { syncPinnedHeight(); + //Restore the devider position. masterDetailPane.setDividerPosition(dividerPosition); } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListChart.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListChart.java index 2dd951a961..b01e9a605a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListChart.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListChart.java @@ -19,6 +19,7 @@ import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.MouseEvent; import org.sleuthkit.autopsy.timeline.TimeLineController; +import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType; import org.sleuthkit.autopsy.timeline.ui.IntervalSelector; import org.sleuthkit.autopsy.timeline.ui.TimeLineChart; import org.sleuthkit.datamodel.TskData; diff --git a/docs/doxygen-user/timeline.dox b/docs/doxygen-user/timeline.dox index 35f661d68e..df72ff529e 100755 --- a/docs/doxygen-user/timeline.dox +++ b/docs/doxygen-user/timeline.dox @@ -136,9 +136,9 @@ Visualization Area: Counts View ------------------------------- The Counts View shows a stacked bar chart with time periods along the x-axis and event counts along the y-axis. The height of each bar represents the number of events that occurred in that time period. The different colored segments represent different event types. Right clicking the bars brings up a context menu with selection and zooming actions. -The only setting specific to the Counts View is what kind of vertical scale to use. The default linear scale is good for many use cases. When this scale is selected, the height of the bars represents the counts in a linear, one-to-one fashion, and the y-axis is labeled with values. When the range of count values is very large, date ranges with relatively low counts have a bar that may be too small to see. To help avoid the misperception of this as no events, the labels for time periods with events are bold relative to the labels for time periods with no events. -To see the events when the bar for a period is too small, there are three options: adjust the window size so that the visualization area has more vertical space, adjust the time range shown so that time periods with relatively much larger bars are excluded, or adjust the scale setting to logarithmic. The logarithmic scale represents the number of events in a non-linear way that compresses the difference between very large and very small numbers. Note that even with the logarithmic scale, an extremely large difference in counts may still produce bars too small to see. In this case the only option may be to exclude events to reduce the difference in counts. -Because the logarithmic scale is applied to each event type separately, the height of the combined bar is not very meaningful, and to emphasize this, no labels are shown on the y-axis. The logarithmic scale should be used to quickly compare the counts relative across _time within a type, or across types for one time period, but not both_. The exact numbers (available in tooltips or the result viewer) should be used for absolute comparisons. Use the logarithmic scales with care. +The only setting specific to the Counts View is what kind of vertical scale to use: The linear scale is good for many use cases. When this scale is selected, the height of the bars represents the counts in a linear, one-to-one fashion, and the y-axis is labeled with values. When the range of values is very large, time periods with low counts may have a bar that is too small to see. To help the user detect this, the labels for date ranges with events are bold. To see bars that are too small, there are three options: adjust the window size so that the visualization area has more vertical space, adjust the time range shown so that time periods with larger bars are excluded, or adjust the scale setting to logarithmic. + +The logarithmic scale represents the number of events in a non-linear way that compresses the difference between large and small numbers. Note that even with the logarithmic scale, an extremely large difference in counts may still produce bars too small to see. In this case the only option may be to filter events to reduce the difference in counts. NOTE: Because the logarithmic scale is applied to each event type separately, the meaning of the height of the combined bar is not intuitive, and to emphasize this, no labels are shown on the y-axis with the logarithmic scale. The logarithmic scale should be used to quickly compare the counts *across time within a type, or across types for one time period, but not both.* The actual counts (available in tooltips or the result viewer) should be used for absolute comparisons. Use the logarithmic scale with care.