changeset 194:af656f596752

Bug 3247: Refactoring chart legends and tooltip Reviewed-by: ykubota https://github.com/HeapStats/heapstats/pull/57
author Yasumasa Suenaga <yasuenag@gmail.com>
date Mon, 12 Dec 2016 22:04:39 +0900
parents d70bd5dab532
children 09afe400f30a
files ChangeLog analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/log/tabs/LogResourcesController.java analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/snapshot/tabs/SummaryController.java analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/utils/HeapStatsUtils.java analyzer/fx/src/main/resources/jp/co/ntt/oss/heapstats/plugin/builtin/log/tabs/resources.fxml analyzer/fx/src/main/resources/jp/co/ntt/oss/heapstats/plugin/builtin/snapshot/tabs/summary.fxml
diffstat 6 files changed, 401 insertions(+), 82 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed Dec 07 21:12:08 2016 +0900
+++ b/ChangeLog	Mon Dec 12 22:04:39 2016 +0900
@@ -1,3 +1,7 @@
+2016-12-12  Yasumasa Suenaga <yasuenag@gmail.com>
+
+	* Bug 3247: Refactoring chart legends and tooltip
+
 2016-12-07  Yasumasa Suenaga <yasuenag@gmail.com>
 
 	* Bug 3259: [REFACTORING] color selection for chart classes
--- a/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/log/tabs/LogResourcesController.java	Wed Dec 07 21:12:08 2016 +0900
+++ b/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/log/tabs/LogResourcesController.java	Mon Dec 12 22:04:39 2016 +0900
@@ -21,10 +21,9 @@
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.List;
-import java.util.Optional;
 import java.util.ResourceBundle;
-import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 import java.util.stream.Stream;
 import javafx.application.Platform;
 import javafx.beans.property.ObjectProperty;
@@ -32,6 +31,7 @@
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
 import javafx.concurrent.Task;
+import javafx.event.EventHandler;
 import javafx.fxml.FXML;
 import javafx.fxml.Initializable;
 import javafx.scene.Node;
@@ -40,16 +40,18 @@
 import javafx.scene.chart.NumberAxis;
 import javafx.scene.chart.StackedAreaChart;
 import javafx.scene.chart.XYChart;
+import javafx.scene.control.ContentDisplay;
 import javafx.scene.control.Label;
 import javafx.scene.control.TableColumn;
 import javafx.scene.control.TableView;
+import javafx.scene.control.Tooltip;
 import javafx.scene.control.cell.PropertyValueFactory;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.layout.AnchorPane;
 import javafx.scene.layout.GridPane;
 import javafx.scene.layout.StackPane;
+import javafx.scene.shape.Path;
 import javafx.scene.shape.Rectangle;
-import javafx.stage.Popup;
 import jp.co.ntt.oss.heapstats.container.log.ArchiveData;
 import jp.co.ntt.oss.heapstats.container.log.DiffData;
 import jp.co.ntt.oss.heapstats.container.log.LogData;
@@ -129,10 +131,6 @@
     @FXML
     private TableColumn<SummaryData.SummaryDataEntry, String> valueColumn;
 
-    private Popup chartPopup;
-
-    private Label popupText;
-
     private ObjectProperty<ObservableList<ArchiveData>> archiveList;
 
     private List<LocalDateTime> suspectList;
@@ -141,6 +139,170 @@
     
     private EpochTimeConverter epochTimeConverter;
 
+    /* Tooltip for Java CPU chart */
+    private Tooltip javaCPUTooltip;
+
+    private GridPane javaCPUTooltipGrid;
+
+    private Label javaUserLabel;
+
+    private Label javaSysLabel;
+
+    /* Tooltip for System CPU chart */
+    private Tooltip systemCPUTooltip;
+
+    private GridPane systemCPUTooltipGrid;
+
+    private Label systemUserLabel;
+
+    private Label systemNiceLabel;
+
+    private Label systemSysLabel;
+
+    private Label systemIdleLabel;
+
+    private Label systemIOWaitLabel;
+
+    private Label systemIRQLabel;
+
+    private Label systemSoftIRQLabel;
+
+    private Label systemStealLabel;
+
+    private Label systemGuestLabel;
+
+    /* Tooltip for Java Memory chart */
+    private Tooltip javaMemoryTooltip;
+
+    private GridPane javaMemoryTooltipGrid;
+
+    private Label javaMemoryVSZLabel;
+
+    private Label javaMemoryRSSLabel;
+
+    /* Generic Tooltip */
+    private Tooltip tooltip;
+
+    private void initializeJavaCPUTooltip(){
+        javaUserLabel = new Label();
+        javaSysLabel = new Label();
+
+        Rectangle javaUserRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
+        Rectangle javaSysRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
+
+        Platform.runLater(() -> {
+            javaUserRect.setStyle("-fx-fill: " + ((Path)javaCPUChart.lookup(".series0")).getFill().toString().replace("0x", "#"));
+            javaSysRect.setStyle("-fx-fill: " + ((Path)javaCPUChart.lookup(".series1")).getFill().toString().replace("0x", "#"));
+        });
+
+        javaCPUTooltipGrid = new GridPane();
+        javaCPUTooltipGrid.setHgap(HeapStatsUtils.TOOLTIP_GRIDPANE_GAP);
+        javaCPUTooltipGrid.add(javaUserRect, 0, 0);
+        javaCPUTooltipGrid.add(new Label("user"), 1, 0);
+        javaCPUTooltipGrid.add(javaUserLabel, 2, 0);
+        javaCPUTooltipGrid.add(javaSysRect, 0, 1);
+        javaCPUTooltipGrid.add(new Label("sys"), 1, 1);
+        javaCPUTooltipGrid.add(javaSysLabel, 2, 1);
+
+        javaCPUTooltip = new Tooltip();
+        javaCPUTooltip.setGraphic(javaCPUTooltipGrid);
+        javaCPUTooltip.setContentDisplay(ContentDisplay.BOTTOM);
+    }
+
+    private void initializeJavaMemoryTooltip(){
+        javaMemoryVSZLabel = new Label();
+        javaMemoryRSSLabel = new Label();
+
+        Rectangle javaMemoryVSZRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
+        Rectangle javaMemoryRSSRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
+
+        Platform.runLater(() -> {
+            javaMemoryRSSRect.setStyle("-fx-fill: " + ((Path)javaCPUChart.lookup(".series0")).getFill().toString().replace("0x", "#"));
+            javaMemoryVSZRect.setStyle("-fx-fill: " + ((Path)javaCPUChart.lookup(".series1")).getFill().toString().replace("0x", "#"));
+        });
+
+        javaMemoryTooltipGrid = new GridPane();
+        javaMemoryTooltipGrid.setHgap(HeapStatsUtils.TOOLTIP_GRIDPANE_GAP);
+        javaMemoryTooltipGrid.add(javaMemoryVSZRect, 0, 0);
+        javaMemoryTooltipGrid.add(new Label("VSZ"), 1, 0);
+        javaMemoryTooltipGrid.add(javaMemoryVSZLabel, 2, 0);
+        javaMemoryTooltipGrid.add(javaMemoryRSSRect, 0, 1);
+        javaMemoryTooltipGrid.add(new Label("RSS"), 1, 1);
+        javaMemoryTooltipGrid.add(javaMemoryRSSLabel, 2, 1);
+
+        javaMemoryTooltip = new Tooltip();
+        javaMemoryTooltip.setGraphic(javaMemoryTooltipGrid);
+        javaMemoryTooltip.setContentDisplay(ContentDisplay.BOTTOM);
+    }
+
+    private void initializeSystemCPUTooltip(){
+        systemUserLabel = new Label();
+        systemNiceLabel = new Label();
+        systemSysLabel = new Label();
+        systemIdleLabel = new Label();
+        systemIOWaitLabel = new Label();
+        systemIRQLabel = new Label();
+        systemSoftIRQLabel = new Label();
+        systemStealLabel = new Label();
+        systemGuestLabel = new Label();
+
+        Rectangle systemUserRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
+        Rectangle systemNiceRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
+        Rectangle systemSysRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
+        Rectangle systemIdleRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
+        Rectangle systemIOWaitRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
+        Rectangle systemIRQRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
+        Rectangle systemSoftIRQRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
+        Rectangle systemStealRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
+        Rectangle systemGuestRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
+
+        Platform.runLater(() -> {
+            systemUserRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series0")).getFill().toString().replace("0x", "#"));
+            systemNiceRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series1")).getFill().toString().replace("0x", "#"));
+            systemSysRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series2")).getFill().toString().replace("0x", "#"));
+            systemIdleRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series3")).getFill().toString().replace("0x", "#"));
+            systemIOWaitRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series4")).getFill().toString().replace("0x", "#"));
+            systemIRQRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series5")).getFill().toString().replace("0x", "#"));
+            systemSoftIRQRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series6")).getFill().toString().replace("0x", "#"));
+            systemStealRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series7")).getFill().toString().replace("0x", "#"));
+            systemGuestRect.setStyle("-fx-fill: " + ((Path)systemCPUChart.lookup(".series8")).getFill().toString().replace("0x", "#"));
+        });
+
+        systemCPUTooltipGrid = new GridPane();
+        systemCPUTooltipGrid.setHgap(HeapStatsUtils.TOOLTIP_GRIDPANE_GAP);
+        systemCPUTooltipGrid.add(systemUserRect, 0, 0);
+        systemCPUTooltipGrid.add(new Label("user"), 1, 0);
+        systemCPUTooltipGrid.add(systemUserLabel, 2, 0);
+        systemCPUTooltipGrid.add(systemNiceRect, 0, 1);
+        systemCPUTooltipGrid.add(new Label("nice"), 1, 1);
+        systemCPUTooltipGrid.add(systemNiceLabel, 2, 1);
+        systemCPUTooltipGrid.add(systemSysRect, 0, 2);
+        systemCPUTooltipGrid.add(new Label("sys"), 1, 2);
+        systemCPUTooltipGrid.add(systemSysLabel, 2, 2);
+        systemCPUTooltipGrid.add(systemIdleRect, 0, 3);
+        systemCPUTooltipGrid.add(new Label("idle"), 1, 3);
+        systemCPUTooltipGrid.add(systemIdleLabel, 2, 3);
+        systemCPUTooltipGrid.add(systemIOWaitRect, 0, 4);
+        systemCPUTooltipGrid.add(new Label("iowait"), 1, 4);
+        systemCPUTooltipGrid.add(systemIOWaitLabel, 2, 4);
+        systemCPUTooltipGrid.add(systemIRQRect, 0, 5);
+        systemCPUTooltipGrid.add(new Label("IRQ"), 1, 5);
+        systemCPUTooltipGrid.add(systemIRQLabel, 2, 5);
+        systemCPUTooltipGrid.add(systemSoftIRQRect, 0, 6);
+        systemCPUTooltipGrid.add(new Label("Soft IRQ"), 1, 6);
+        systemCPUTooltipGrid.add(systemSoftIRQLabel, 2, 6);
+        systemCPUTooltipGrid.add(systemStealRect, 0, 7);
+        systemCPUTooltipGrid.add(new Label("steal"), 1, 7);
+        systemCPUTooltipGrid.add(systemStealLabel, 2, 7);
+        systemCPUTooltipGrid.add(systemGuestRect, 0, 8);
+        systemCPUTooltipGrid.add(new Label("guest"), 1, 8);
+        systemCPUTooltipGrid.add(systemGuestLabel, 2, 8);
+
+        systemCPUTooltip = new Tooltip();
+        systemCPUTooltip.setGraphic(systemCPUTooltipGrid);
+        systemCPUTooltip.setContentDisplay(ContentDisplay.BOTTOM);
+    }
+
     /**
      * Initializes the controller class.
      */
@@ -158,13 +320,12 @@
 
         initializeChartSeries();
 
-        chartPopup = new Popup();
-        popupText = new Label();
-        popupText.setStyle("-fx-font-family: monospace; -fx-text-fill: white; -fx-background-color: black;");
-        chartPopup.getContent().add(popupText);
         archiveList = new SimpleObjectProperty<>();
-        
         epochTimeConverter = new EpochTimeConverter();
+
+        initializeJavaCPUTooltip();
+        initializeSystemCPUTooltip();
+        initializeJavaMemoryTooltip();
     }
 
     /**
@@ -225,60 +386,6 @@
         monitorChart.getData().add(monitors);
     }
 
-    /**
-     * Show popup window with pointing data in chart.
-     *
-     * @param chart Target chart.
-     * @param xValue value in X Axis.
-     * @param event Mouse event.
-     * @param labelFunc Function to format label string.
-     */
-    private void showChartPopup(XYChart<Number, ? extends Number> chart, Number xValue, MouseEvent event, Function<? super Number, String> labelFunc) {
-        boolean isContained = chart.getData()
-                                   .stream()
-                                   .flatMap(s -> s.getData().stream())
-                                   .anyMatch(d -> d.getXValue().longValue() == xValue.longValue());
-        if(isContained){
-            String label = chart.getData()
-                                .stream()
-                                .map(s -> s.getName() + " = " + s.getData()
-                                                                 .stream()
-                                                                 .filter(d -> d.getXValue().longValue() == xValue.longValue())
-                                                                 .map(d -> labelFunc.apply(d.getYValue()))
-                                                                 .findAny()
-                                                                 .get())
-                                .collect(Collectors.joining("\n"));
-            popupText.setText(epochTimeConverter.toString(xValue) + "\n" + label);
-            chartPopup.show(chart, event.getScreenX() + 15.0d, event.getScreenY() + 3.0d);
-        }
-    }
-
-    @FXML
-    @SuppressWarnings("unchecked")
-    private void onChartMouseMoved(MouseEvent event) {
-        XYChart<Number, ? extends Number> chart = (XYChart<Number, ? extends Number>) event.getSource();
-        NumberAxis xAxis = (NumberAxis)chart.getXAxis();
-        Function<? super Number, String> labelFunc;
-
-        if ((chart == javaCPUChart) || (chart == systemCPUChart)) {
-            labelFunc = d -> String.format("%.02f %%", d);
-        } else if (chart == javaMemoryChart) {
-            labelFunc = d -> String.format("%d MB", d);
-        } else if (chart == safepointTimeChart) {
-            labelFunc = d -> String.format("%d ms", d);
-        } else {
-            labelFunc = d -> d.toString();
-        }
-
-        Optional.ofNullable(chart.getXAxis().getValueForDisplay(event.getX() - xAxis.getLayoutX()))
-                .ifPresent(v -> showChartPopup(chart, v, event, labelFunc));
-    }
-
-    @FXML
-    private void onChartMouseExited(MouseEvent event) {
-        chartPopup.hide();
-    }
-
     private void drawLineInternal(StackPane target, List<Number> drawList, String style) {
         AnchorPane anchor = null;
         XYChart chart = null;
@@ -463,6 +570,82 @@
             updateProgress();
         }
 
+        private void setJavaCPUChartTooltip(int idx){
+            XYChart.Data<Number, Double> userNode = javaUserUsage.getData().get(idx);
+            XYChart.Data<Number, Double> sysNode = javaSysUsage.getData().get(idx);
+
+            EventHandler<MouseEvent> handler = e -> {
+                javaCPUTooltip.setText(epochTimeConverter.toString(userNode.getXValue()));
+                javaUserLabel.setText(String.format("%.02f", userNode.getYValue()) + " %");
+                javaSysLabel.setText(String.format("%.02f", sysNode.getYValue()) + " %");
+            };
+
+            Tooltip.install(userNode.getNode(), javaCPUTooltip);
+            userNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+            Tooltip.install(sysNode.getNode(), javaCPUTooltip);
+            sysNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+        }
+
+        private void setJavaMemoryChartTooltip(int idx){
+            XYChart.Data<Number, Long> vszNode = javaVSZUsage.getData().get(idx);
+            XYChart.Data<Number, Long> rssNode = javaRSSUsage.getData().get(idx);
+
+            EventHandler<MouseEvent> handler = e -> {
+                javaMemoryTooltip.setText(epochTimeConverter.toString(vszNode.getXValue()));
+                javaMemoryVSZLabel.setText(vszNode.getYValue() + " MB");
+                javaMemoryRSSLabel.setText(rssNode.getYValue() + " MB");
+            };
+
+            Tooltip.install(vszNode.getNode(), javaMemoryTooltip);
+            vszNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+            Tooltip.install(rssNode.getNode(), javaMemoryTooltip);
+            rssNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+        }
+
+        private void setSystemCPUChartTooltip(int idx){
+            XYChart.Data<Number, Double> userNode = systemUserUsage.getData().get(idx);
+            XYChart.Data<Number, Double> niceNode = systemNiceUsage.getData().get(idx);
+            XYChart.Data<Number, Double> sysNode = systemSysUsage.getData().get(idx);
+            XYChart.Data<Number, Double> idleNode = systemIdleUsage.getData().get(idx);
+            XYChart.Data<Number, Double> iowaitNode = systemIOWaitUsage.getData().get(idx);
+            XYChart.Data<Number, Double> irqNode = systemIRQUsage.getData().get(idx);
+            XYChart.Data<Number, Double> softIrqNode = systemSoftIRQUsage.getData().get(idx);
+            XYChart.Data<Number, Double> stealNode = systemStealUsage.getData().get(idx);
+            XYChart.Data<Number, Double> guestNode = systemGuestUsage.getData().get(idx);
+
+            EventHandler<MouseEvent> handler = e -> {
+                systemCPUTooltip.setText(epochTimeConverter.toString(userNode.getXValue()));
+                systemUserLabel.setText(String.format("%.02f", userNode.getYValue()) + " %");
+                systemNiceLabel.setText(String.format("%.02f", niceNode.getYValue()) + " %");
+                systemSysLabel.setText(String.format("%.02f", sysNode.getYValue()) + " %");
+                systemIdleLabel.setText(String.format("%.02f", idleNode.getYValue()) + " %");
+                systemIOWaitLabel.setText(String.format("%.02f", iowaitNode.getYValue()) + " %");
+                systemIRQLabel.setText(String.format("%.02f", irqNode.getYValue()) + " %");
+                systemSoftIRQLabel.setText(String.format("%.02f", softIrqNode.getYValue()) + " %");
+                systemStealLabel.setText(String.format("%.02f", stealNode.getYValue()) + " %");
+                systemGuestLabel.setText(String.format("%.02f", guestNode.getYValue()) + " %");
+            };
+
+            Tooltip.install(userNode.getNode(), systemCPUTooltip);
+            userNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+            Tooltip.install(niceNode.getNode(), systemCPUTooltip);
+            niceNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+            Tooltip.install(sysNode.getNode(), systemCPUTooltip);
+            sysNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+            Tooltip.install(idleNode.getNode(), systemCPUTooltip);
+            idleNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+            Tooltip.install(iowaitNode.getNode(), systemCPUTooltip);
+            iowaitNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+            Tooltip.install(irqNode.getNode(), systemCPUTooltip);
+            irqNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+            Tooltip.install(softIrqNode.getNode(), systemCPUTooltip);
+            softIrqNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+            Tooltip.install(stealNode.getNode(), systemCPUTooltip);
+            stealNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+            Tooltip.install(guestNode.getNode(), systemCPUTooltip);
+            guestNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+        }
+
         private void setChartData() {
             /* Set chart range */
             long startLogEpoch = targetLogData.get(0).getDateTime().atZone(ZoneId.systemDefault()).toEpochSecond();
@@ -504,6 +687,22 @@
 
             threads.setData(threadsBuf);
 
+            /* Tooltip setting */
+            IntStream.range(0, targetDiffData.size())
+                     .peek(this::setJavaCPUChartTooltip)
+                     .forEach(this::setSystemCPUChartTooltip);
+            IntStream.range(0, targetLogData.size())
+                     .forEach(this::setJavaMemoryChartTooltip);
+            Tooltip tooltip = new Tooltip();
+            Stream.of(threads, safepoints, monitors)
+                  .flatMap(c -> c.getData().stream())
+                  .peek(d -> Tooltip.install(d.getNode(), tooltip))
+                  .forEach(d -> d.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, e -> tooltip.setText((epochTimeConverter.toString(d.getXValue()) + ": " + d.getYValue()))));
+            safepointTime.getData()
+                         .stream()
+                         .peek(d -> Tooltip.install(d.getNode(), tooltip))
+                         .forEach(d -> d.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, e -> tooltip.setText(String.format("%s: %d ms", epochTimeConverter.toString(d.getXValue()), d.getYValue()))));
+
             /* Put summary data to table */
             SummaryData summary = new SummaryData(targetLogData, targetDiffData);
             procSummary.setItems(FXCollections.observableArrayList(new SummaryData.SummaryDataEntry(resource.getString("summary.cpu.average"), String.format("%.1f %%", summary.getAverageCPUUsage())),
--- a/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/snapshot/tabs/SummaryController.java	Wed Dec 07 21:12:08 2016 +0900
+++ b/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/snapshot/tabs/SummaryController.java	Mon Dec 12 22:04:39 2016 +0900
@@ -23,6 +23,7 @@
 import java.util.ResourceBundle;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 import java.util.stream.Stream;
 import javafx.application.Platform;
 import javafx.beans.property.ObjectProperty;
@@ -31,6 +32,7 @@
 import javafx.collections.ObservableList;
 import javafx.collections.ObservableSet;
 import javafx.concurrent.Task;
+import javafx.event.EventHandler;
 import javafx.fxml.FXML;
 import javafx.fxml.Initializable;
 import javafx.scene.chart.AreaChart;
@@ -38,12 +40,18 @@
 import javafx.scene.chart.NumberAxis;
 import javafx.scene.chart.StackedAreaChart;
 import javafx.scene.chart.XYChart;
+import javafx.scene.control.ContentDisplay;
+import javafx.scene.control.Label;
 import javafx.scene.control.TableColumn;
 import javafx.scene.control.TableView;
 import javafx.scene.control.Tooltip;
 import javafx.scene.control.cell.PropertyValueFactory;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.GridPane;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Path;
+import javafx.scene.shape.Rectangle;
 import jp.co.ntt.oss.heapstats.container.snapshot.SnapShotHeader;
 import jp.co.ntt.oss.heapstats.container.snapshot.SummaryData;
 import jp.co.ntt.oss.heapstats.utils.EpochTimeConverter;
@@ -97,7 +105,23 @@
 
     private EpochTimeConverter epochTimeConverter;
 
-    private Tooltip tooltip;
+    private Tooltip heapTooltip;
+
+    private GridPane heapTooltipGrid;
+
+    private Label youngLabel;
+
+    private Label oldLabel;
+
+    private Label freeLabel;
+
+    private Tooltip metaspaceTooltip;
+
+    private GridPane metaspaceTooltipGrid;
+
+    private Label metaspaceUsageLabel;
+
+    private Label metaspaceCapacityLabel;
 
     /**
      * Initializes the controller class.
@@ -149,8 +173,6 @@
      */
     @SuppressWarnings("unchecked")
     private void initializeChartSeries() {
-        tooltip = new Tooltip();
-
         youngUsage = new XYChart.Series<>();
         youngUsage.setName("Young");
         oldUsage = new XYChart.Series<>();
@@ -181,6 +203,52 @@
         metaspaceUsage = new XYChart.Series<>();
         metaspaceUsage.setName("Usage");
         metaspaceChart.getData().addAll(metaspaceCapacity, metaspaceUsage);
+
+        /* Tooltip setup */
+        /* Java heap */
+        heapTooltipGrid = new GridPane();
+        heapTooltipGrid.setHgap(HeapStatsUtils.TOOLTIP_GRIDPANE_GAP);
+        youngLabel = new Label();
+        oldLabel = new Label();
+        freeLabel = new Label();
+        Rectangle youngRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, Color.web(heapChartColors[0]));
+        Rectangle oldRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, Color.web(heapChartColors[1]));
+        Rectangle freeRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, Color.web(heapChartColors[2]));
+        heapTooltipGrid.add(youngRect, 0, 0);
+        heapTooltipGrid.add(new Label("Young"), 1, 0);
+        heapTooltipGrid.add(youngLabel, 2, 0);
+        heapTooltipGrid.add(oldRect, 0, 1);
+        heapTooltipGrid.add(new Label("Old"), 1, 1);
+        heapTooltipGrid.add(oldLabel, 2, 1);
+        heapTooltipGrid.add(freeRect, 0, 2);
+        heapTooltipGrid.add(new Label("Free"), 1, 2);
+        heapTooltipGrid.add(freeLabel, 2, 2);
+        heapTooltip = new Tooltip();
+        heapTooltip.setGraphic(heapTooltipGrid);
+        heapTooltip.setContentDisplay(ContentDisplay.BOTTOM);
+
+        /* Metaspace */
+        metaspaceTooltipGrid = new GridPane();
+        metaspaceTooltipGrid.setHgap(HeapStatsUtils.TOOLTIP_GRIDPANE_GAP);
+        metaspaceUsageLabel = new Label();
+        metaspaceCapacityLabel = new Label();
+        Rectangle metaUsageRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
+        Rectangle metaCapacityRect = new Rectangle(HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE, HeapStatsUtils.TOOLTIP_LEGEND_RECT_SIZE);
+
+        Platform.runLater(() -> {
+            metaUsageRect.setStyle("-fx-fill: " + ((Path)metaspaceChart.lookup(".default-color0.chart-series-area-fill")).getFill().toString().replace("0x", "#"));
+            metaCapacityRect.setStyle("-fx-fill: " + ((Path)metaspaceChart.lookup(".default-color1.chart-series-area-fill")).getFill().toString().replace("0x", "#"));
+        });
+
+        metaspaceTooltipGrid.add(metaUsageRect, 0, 0);
+        metaspaceTooltipGrid.add(new Label("Usage"), 1, 0);
+        metaspaceTooltipGrid.add(metaspaceUsageLabel, 2, 0);
+        metaspaceTooltipGrid.add(metaCapacityRect, 0, 1);
+        metaspaceTooltipGrid.add(new Label("Capacity"), 1, 1);
+        metaspaceTooltipGrid.add(metaspaceCapacityLabel, 2, 1);
+        metaspaceTooltip = new Tooltip();
+        metaspaceTooltip.setGraphic(metaspaceTooltipGrid);
+        metaspaceTooltip.setContentDisplay(ContentDisplay.BOTTOM);
     }
 
     /**
@@ -257,6 +325,47 @@
             return null;
         }
 
+        private void setupJavaHeapChartTooltip(int idx){
+            XYChart.Data<Number, Long> youngNode = youngUsage.getData().get(idx);
+            XYChart.Data<Number, Long> oldNode = oldUsage.getData().get(idx);
+            XYChart.Data<Number, Long> freeNode = free.getData().get(idx);
+            long youngInMB = youngNode.getYValue();
+            long oldInMB = oldNode.getYValue();
+            long freeInMB = freeNode.getYValue();
+
+            EventHandler<MouseEvent> handler = e -> {
+                heapTooltip.setText(epochTimeConverter.toString(youngNode.getXValue()));
+                youngLabel.setText(youngInMB + " MB");
+                oldLabel.setText(oldInMB + " MB");
+                freeLabel.setText(freeInMB + " MB");
+            };
+
+            Tooltip.install(youngNode.getNode(), heapTooltip);
+            youngNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+            Tooltip.install(oldNode.getNode(), heapTooltip);
+            oldNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+            Tooltip.install(freeNode.getNode(), heapTooltip);
+            freeNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+        }
+
+        private void setupMetaspaceChartTooltip(int idx){
+            XYChart.Data<Number, Long> usageNode = metaspaceUsage.getData().get(idx);
+            XYChart.Data<Number, Long> capacityNode = metaspaceCapacity.getData().get(idx);
+            long usage = usageNode.getYValue();
+            long capacity = capacityNode.getYValue();
+
+            EventHandler<MouseEvent> handler = e -> {
+                metaspaceTooltip.setText(epochTimeConverter.toString(usageNode.getXValue()));
+                metaspaceUsageLabel.setText(usage + " MB");
+                metaspaceCapacityLabel.setText(capacity + " MB");
+            };
+
+            Tooltip.install(usageNode.getNode(), metaspaceTooltip);
+            usageNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+            Tooltip.install(capacityNode.getNode(), metaspaceTooltip);
+            capacityNode.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, handler);
+        }
+
         @Override
         protected void succeeded() {
             long startEpoch = currentTarget.get().get(0).getSnapShotDate().atZone(ZoneId.systemDefault()).toEpochSecond();
@@ -281,10 +390,11 @@
 
             /* Set tooltip */
             /* Java Heap & Metaspace */
-            Stream.of(youngUsage, oldUsage, free, metaspaceUsage, metaspaceCapacity)
-                  .flatMap(s -> s.getData().stream())
-                  .peek(d -> Tooltip.install(d.getNode(), tooltip))
-                  .forEach(d -> d.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, e -> tooltip.setText(String.format("%s: %d MB", epochTimeConverter.toString(d.getXValue()), d.getYValue()))));
+            IntStream.range(0, currentTarget.get().size())
+                     .peek(this::setupJavaHeapChartTooltip)
+                     .forEach(this::setupMetaspaceChartTooltip);
+
+            Tooltip tooltip = new Tooltip();
 
             /* Insatances */
             instances.getData()
--- a/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/utils/HeapStatsUtils.java	Wed Dec 07 21:12:08 2016 +0900
+++ b/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/utils/HeapStatsUtils.java	Mon Dec 12 22:04:39 2016 +0900
@@ -54,6 +54,12 @@
  */
 public class HeapStatsUtils {
 
+    /* Rect size in Tooltip for chart legend. */
+    public static final double TOOLTIP_LEGEND_RECT_SIZE = 10.0d;
+
+    /* Gap between each controls in Tooltip. */
+    public static final double TOOLTIP_GRIDPANE_GAP = 5.0d;
+
     /* Path of HeapStats home directory. */
     private static Path currentPath = null;
 
--- a/analyzer/fx/src/main/resources/jp/co/ntt/oss/heapstats/plugin/builtin/log/tabs/resources.fxml	Wed Dec 07 21:12:08 2016 +0900
+++ b/analyzer/fx/src/main/resources/jp/co/ntt/oss/heapstats/plugin/builtin/log/tabs/resources.fxml	Mon Dec 12 22:04:39 2016 +0900
@@ -39,7 +39,7 @@
             <children>
                 <StackPane GridPane.columnIndex="1" GridPane.rowIndex="1">
                     <children>
-                        <LineChart id="threadChart" fx:id="threadChart" animated="false" createSymbols="false" legendVisible="false" minHeight="0.0" minWidth="0.0" onMouseExited="#onChartMouseExited" onMouseMoved="#onChartMouseMoved" title="%chart.thread">
+                        <LineChart id="threadChart" fx:id="threadChart" animated="false" legendVisible="false" minHeight="0.0" minWidth="0.0" title="%chart.thread">
                             <xAxis>
                                 <NumberAxis side="BOTTOM" tickLabelsVisible="false" minorTickVisible="false" autoRanging="false" />
                             </xAxis>
@@ -61,7 +61,7 @@
                 </TableView>
                 <StackPane minHeight="0.0" minWidth="0.0">
                     <children>
-                        <StackedAreaChart id="javaCPUChart" fx:id="javaCPUChart" animated="false" createSymbols="false" legendSide="BOTTOM" legendVisible="true" minHeight="0.0" minWidth="0.0" onMouseExited="#onChartMouseExited" onMouseMoved="#onChartMouseMoved" title="%chart.javacpu">
+                        <StackedAreaChart id="javaCPUChart" fx:id="javaCPUChart" animated="false" legendVisible="false" minHeight="0.0" minWidth="0.0" title="%chart.javacpu">
                             <xAxis>
                                 <NumberAxis side="BOTTOM" tickLabelsVisible="false" minorTickVisible="false" autoRanging="false" />
                             </xAxis>
@@ -74,7 +74,7 @@
                 </StackPane>
                 <StackPane minHeight="0.0" minWidth="0.0" GridPane.columnIndex="1">
                     <children>
-                        <StackedAreaChart id="systemCPUChart" fx:id="systemCPUChart" animated="false" createSymbols="false" legendSide="BOTTOM" legendVisible="true" minHeight="0.0" minWidth="0.0" onMouseExited="#onChartMouseExited" onMouseMoved="#onChartMouseMoved" title="%chart.systemcpu">
+                        <StackedAreaChart id="systemCPUChart" fx:id="systemCPUChart" animated="false" legendVisible="false" minHeight="0.0" minWidth="0.0" title="%chart.systemcpu">
                             <xAxis>
                                 <NumberAxis side="BOTTOM" tickLabelsVisible="false" minorTickVisible="false" autoRanging="false" />
                             </xAxis>
@@ -87,7 +87,7 @@
                 </StackPane>
                 <StackPane minHeight="0.0" minWidth="0.0" GridPane.rowIndex="1">
                     <children>
-                        <LineChart id="javaMemoryChart" fx:id="javaMemoryChart" animated="false" createSymbols="false" legendSide="BOTTOM" legendVisible="true" minHeight="0.0" minWidth="0.0" onMouseExited="#onChartMouseExited" onMouseMoved="#onChartMouseMoved" title="%chart.nativememory">
+                        <LineChart id="javaMemoryChart" fx:id="javaMemoryChart" animated="false" legendVisible="false" minHeight="0.0" minWidth="0.0" title="%chart.nativememory">
                             <xAxis>
                                 <NumberAxis side="BOTTOM" tickLabelsVisible="false" minorTickVisible="false" autoRanging="false" />
                             </xAxis>
@@ -100,7 +100,7 @@
                 </StackPane>
                 <StackPane minHeight="0.0" minWidth="0.0" GridPane.rowIndex="2">
                     <children>
-                        <LineChart id="safepointChart" fx:id="safepointChart" animated="false" createSymbols="false" legendVisible="false" minHeight="0.0" minWidth="0.0" onMouseExited="#onChartMouseExited" onMouseMoved="#onChartMouseMoved" title="%chart.safepoint.count">
+                        <LineChart id="safepointChart" fx:id="safepointChart" animated="false" legendVisible="false" minHeight="0.0" minWidth="0.0" title="%chart.safepoint.count">
                             <xAxis>
                                 <NumberAxis side="BOTTOM" tickLabelsVisible="false" minorTickVisible="false" visible="false" autoRanging="false" />
                             </xAxis>
@@ -113,7 +113,7 @@
                 </StackPane>
                 <StackPane minHeight="0.0" minWidth="0.0" GridPane.columnIndex="1" GridPane.rowIndex="2">
                     <children>
-                        <LineChart id="safepointTimeChart" fx:id="safepointTimeChart" animated="false" createSymbols="false" legendVisible="false" minHeight="0.0" minWidth="0.0" onMouseExited="#onChartMouseExited" onMouseMoved="#onChartMouseMoved" title="%chart.safepoint.time">
+                        <LineChart id="safepointTimeChart" fx:id="safepointTimeChart" animated="false" legendVisible="false" minHeight="0.0" minWidth="0.0" title="%chart.safepoint.time">
                             <xAxis>
                                 <NumberAxis side="BOTTOM" tickLabelsVisible="false" minorTickVisible="false" visible="false" autoRanging="false" />
                             </xAxis>
@@ -126,7 +126,7 @@
                 </StackPane>
                 <StackPane minHeight="0.0" minWidth="0.0" GridPane.rowIndex="3">
                     <children>
-                        <LineChart id="monitorChart" fx:id="monitorChart" animated="false" createSymbols="false" legendVisible="false" minHeight="0.0" minWidth="0.0" onMouseExited="#onChartMouseExited" onMouseMoved="#onChartMouseMoved" title="%chart.monitorcontention">
+                        <LineChart id="monitorChart" fx:id="monitorChart" animated="false" legendVisible="false" minHeight="0.0" minWidth="0.0" title="%chart.monitorcontention">
                             <xAxis>
                                 <NumberAxis side="BOTTOM" tickLabelsVisible="false" minorTickVisible="false" autoRanging="false" />
                             </xAxis>
--- a/analyzer/fx/src/main/resources/jp/co/ntt/oss/heapstats/plugin/builtin/snapshot/tabs/summary.fxml	Wed Dec 07 21:12:08 2016 +0900
+++ b/analyzer/fx/src/main/resources/jp/co/ntt/oss/heapstats/plugin/builtin/snapshot/tabs/summary.fxml	Mon Dec 12 22:04:39 2016 +0900
@@ -44,7 +44,7 @@
                     <items>
                         <StackPane prefHeight="150.0" prefWidth="200.0">
                             <children>
-                                <StackedAreaChart id="heapChart" fx:id="heapChart" animated="false" layoutX="-129.0" layoutY="-91.0" minHeight="0.0" title="%chart.javaheap" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
+                                <StackedAreaChart id="heapChart" fx:id="heapChart" animated="false" layoutX="-129.0" layoutY="-91.0" minHeight="0.0" title="%chart.javaheap" legendVisible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
                                     <xAxis>
                                         <NumberAxis side="BOTTOM" tickLabelsVisible="false" minorTickVisible="false" autoRanging="false" />
                                     </xAxis>
@@ -83,7 +83,7 @@
                         </StackPane>
                         <StackPane prefHeight="150.0" prefWidth="200.0">
                             <children>
-                                <AreaChart id="metaspaceChart" fx:id="metaspaceChart" animated="false" layoutX="-174.0" layoutY="-166.0" minHeight="0.0" title="%chart.metaspace" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
+                                <AreaChart id="metaspaceChart" fx:id="metaspaceChart" animated="false" layoutX="-174.0" layoutY="-166.0" minHeight="0.0" title="%chart.metaspace" legendVisible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
                                     <xAxis>
                                         <NumberAxis side="BOTTOM" tickLabelsVisible="false" minorTickVisible="false" autoRanging="false" />
                                     </xAxis>