You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by vf...@apache.org on 2016/01/13 18:14:40 UTC

[15/15] incubator-geode git commit: GEODE-628: Rename container folder to "geode-jvsd". - The jvsd code now sits in the geode-jvsd directory. - Files "pom.xml", "README.txt" and "runjvsd.sh" updated.

GEODE-628: Rename container folder to "geode-jvsd".
	- The jvsd code now sits in the geode-jvsd directory.
	- Files "pom.xml", "README.txt" and "runjvsd.sh" updated.


Project: http://git-wip-us.apache.org/repos/asf/incubator-geode/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-geode/commit/8a86c01a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-geode/tree/8a86c01a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-geode/diff/8a86c01a

Branch: refs/heads/feature/GEODE-78
Commit: 8a86c01a367ddbef5a0b11a9091b9d00d6ce405c
Parents: 8c8a9cb
Author: jujoramos <ju...@gmail.com>
Authored: Tue Dec 8 16:09:29 2015 +0000
Committer: jujoramos <ju...@gmail.com>
Committed: Tue Dec 8 16:09:29 2015 +0000

----------------------------------------------------------------------
 geode-jvsd/.gitignore                           |    7 +
 geode-jvsd/README.txt                           |   66 +
 geode-jvsd/pom.xml                              |  155 +
 geode-jvsd/runjvsd.sh                           |    8 +
 .../org.jdesktop.application.Application        |    1 +
 .../geode/jvsd/chart/AdvancedLineChart.java     |  703 ++++
 .../chart/util/LargestTriangleThreeBuckets.java |  102 +
 .../geode/jvsd/controller/RootController.java   |  239 ++
 .../org/apache/geode/jvsd/fx/ChartManager.java  |  100 +
 .../geode/jvsd/fx/HoveredThresholdNode.java     |   49 +
 .../jvsd/fx/LongDoubleMemoryMappedSeries.java   |  115 +
 .../java/org/apache/geode/jvsd/fx/Main.java     |   62 +
 .../geode/jvsd/fx/ObservableByteBuffer.java     |   21 +
 .../geode/jvsd/fx/ObservableDataBuffer.java     |   44 +
 .../apache/geode/jvsd/fx/StatFileManager.java   |   57 +
 .../apache/geode/jvsd/fx/VSDChartWindow.java    |  288 ++
 .../org/apache/geode/jvsd/fx/VsdSeries.java     |   51 +
 .../jvsd/javafx/scene/chart/AbstractSeries.java |  289 ++
 .../jvsd/javafx/scene/chart/BasicDataSet.java   |  152 +
 .../scene/chart/ByteBufferNumberSeries.java     |  450 +++
 .../javafx/scene/chart/DataChangedListener.java |   21 +
 .../geode/jvsd/javafx/scene/chart/DataSet.java  |   29 +
 .../geode/jvsd/javafx/scene/chart/DateAxis.java |  393 ++
 .../geode/jvsd/model/ResourceWrapper.java       |   73 +
 .../geode/jvsd/model/stats/StatArchiveFile.java | 3458 ++++++++++++++++++
 .../jvsd/model/stats/StatArchiveFormat.java     |  200 +
 .../org/apache/geode/jvsd/stats/Utility.java    |  236 ++
 .../VisualDisplayToolAboutBox.properties        |   14 +
 .../resources/VisualDisplayToolApp.properties   |   11 +
 .../resources/VisualDisplayToolView.properties  |   53 +
 .../java/visualdisplaytool/resources/about.png  |  Bin 0 -> 8187 bytes
 .../resources/busyicons/busy-icon0.png          |  Bin 0 -> 3588 bytes
 .../resources/busyicons/busy-icon1.png          |  Bin 0 -> 3585 bytes
 .../resources/busyicons/busy-icon10.png         |  Bin 0 -> 3568 bytes
 .../resources/busyicons/busy-icon11.png         |  Bin 0 -> 3581 bytes
 .../resources/busyicons/busy-icon12.png         |  Bin 0 -> 3589 bytes
 .../resources/busyicons/busy-icon13.png         |  Bin 0 -> 3586 bytes
 .../resources/busyicons/busy-icon14.png         |  Bin 0 -> 3586 bytes
 .../resources/busyicons/busy-icon2.png          |  Bin 0 -> 3585 bytes
 .../resources/busyicons/busy-icon3.png          |  Bin 0 -> 3572 bytes
 .../resources/busyicons/busy-icon4.png          |  Bin 0 -> 3576 bytes
 .../resources/busyicons/busy-icon5.png          |  Bin 0 -> 3580 bytes
 .../resources/busyicons/busy-icon6.png          |  Bin 0 -> 3581 bytes
 .../resources/busyicons/busy-icon7.png          |  Bin 0 -> 3598 bytes
 .../resources/busyicons/busy-icon8.png          |  Bin 0 -> 3594 bytes
 .../resources/busyicons/busy-icon9.png          |  Bin 0 -> 3581 bytes
 .../resources/busyicons/idle-icon.png           |  Bin 0 -> 3360 bytes
 .../java/visualdisplaytool/resources/splash.png |  Bin 0 -> 21747 bytes
 .../src/main/resources/META-INF/css/style.css   |   49 +
 geode-jvsd/src/main/resources/chart.fxml        |   31 +
 geode-jvsd/src/main/resources/jvsd.css          |    3 +
 geode-jvsd/src/main/resources/jvsd.fxml         |   97 +
 .../java/io/pivotal/jvsd/CopyOfCopyOfTest.java  |   40 +
 .../test/java/io/pivotal/jvsd/CopyOfTest.java   |   45 +
 .../java/io/pivotal/jvsd/Copy_2_of_Test.java    |   59 +
 .../src/test/java/io/pivotal/jvsd/Test.java     |  110 +
 jvsdfx-mm/.gitignore                            |    6 -
 jvsdfx-mm/README.txt                            |   68 -
 jvsdfx-mm/lib/nblibraries.properties            |    4 -
 jvsdfx-mm/pom.xml                               |  156 -
 jvsdfx-mm/runjvsd.sh                            |    9 -
 .../org.jdesktop.application.Application        |    1 -
 .../geode/jvsd/chart/AdvancedLineChart.java     |  703 ----
 .../chart/util/LargestTriangleThreeBuckets.java |  102 -
 .../geode/jvsd/controller/RootController.java   |  239 --
 .../org/apache/geode/jvsd/fx/ChartManager.java  |  100 -
 .../geode/jvsd/fx/HoveredThresholdNode.java     |   49 -
 .../jvsd/fx/LongDoubleMemoryMappedSeries.java   |  115 -
 .../java/org/apache/geode/jvsd/fx/Main.java     |   62 -
 .../geode/jvsd/fx/ObservableByteBuffer.java     |   21 -
 .../geode/jvsd/fx/ObservableDataBuffer.java     |   44 -
 .../apache/geode/jvsd/fx/StatFileManager.java   |   57 -
 .../apache/geode/jvsd/fx/VSDChartWindow.java    |  288 --
 .../org/apache/geode/jvsd/fx/VsdSeries.java     |   51 -
 .../jvsd/javafx/scene/chart/AbstractSeries.java |  289 --
 .../jvsd/javafx/scene/chart/BasicDataSet.java   |  152 -
 .../scene/chart/ByteBufferNumberSeries.java     |  450 ---
 .../javafx/scene/chart/DataChangedListener.java |   21 -
 .../geode/jvsd/javafx/scene/chart/DataSet.java  |   29 -
 .../geode/jvsd/javafx/scene/chart/DateAxis.java |  393 --
 .../geode/jvsd/model/ResourceWrapper.java       |   73 -
 .../geode/jvsd/model/stats/StatArchiveFile.java | 3458 ------------------
 .../jvsd/model/stats/StatArchiveFormat.java     |  200 -
 .../org/apache/geode/jvsd/stats/Utility.java    |  236 --
 .../VisualDisplayToolAboutBox.properties        |   14 -
 .../resources/VisualDisplayToolApp.properties   |   11 -
 .../resources/VisualDisplayToolView.properties  |   53 -
 .../java/visualdisplaytool/resources/about.png  |  Bin 8187 -> 0 bytes
 .../resources/busyicons/busy-icon0.png          |  Bin 3588 -> 0 bytes
 .../resources/busyicons/busy-icon1.png          |  Bin 3585 -> 0 bytes
 .../resources/busyicons/busy-icon10.png         |  Bin 3568 -> 0 bytes
 .../resources/busyicons/busy-icon11.png         |  Bin 3581 -> 0 bytes
 .../resources/busyicons/busy-icon12.png         |  Bin 3589 -> 0 bytes
 .../resources/busyicons/busy-icon13.png         |  Bin 3586 -> 0 bytes
 .../resources/busyicons/busy-icon14.png         |  Bin 3586 -> 0 bytes
 .../resources/busyicons/busy-icon2.png          |  Bin 3585 -> 0 bytes
 .../resources/busyicons/busy-icon3.png          |  Bin 3572 -> 0 bytes
 .../resources/busyicons/busy-icon4.png          |  Bin 3576 -> 0 bytes
 .../resources/busyicons/busy-icon5.png          |  Bin 3580 -> 0 bytes
 .../resources/busyicons/busy-icon6.png          |  Bin 3581 -> 0 bytes
 .../resources/busyicons/busy-icon7.png          |  Bin 3598 -> 0 bytes
 .../resources/busyicons/busy-icon8.png          |  Bin 3594 -> 0 bytes
 .../resources/busyicons/busy-icon9.png          |  Bin 3581 -> 0 bytes
 .../resources/busyicons/idle-icon.png           |  Bin 3360 -> 0 bytes
 .../java/visualdisplaytool/resources/splash.png |  Bin 21747 -> 0 bytes
 .../src/main/resources/META-INF/css/style.css   |   49 -
 jvsdfx-mm/src/main/resources/chart.fxml         |   31 -
 jvsdfx-mm/src/main/resources/jvsd.css           |    3 -
 jvsdfx-mm/src/main/resources/jvsd.fxml          |   97 -
 .../java/io/pivotal/jvsd/CopyOfCopyOfTest.java  |   40 -
 .../test/java/io/pivotal/jvsd/CopyOfTest.java   |   45 -
 .../java/io/pivotal/jvsd/Copy_2_of_Test.java    |   59 -
 .../src/test/java/io/pivotal/jvsd/Test.java     |  110 -
 113 files changed, 7881 insertions(+), 7888 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/.gitignore
----------------------------------------------------------------------
diff --git a/geode-jvsd/.gitignore b/geode-jvsd/.gitignore
new file mode 100644
index 0000000..f733b4b
--- /dev/null
+++ b/geode-jvsd/.gitignore
@@ -0,0 +1,7 @@
+target
+nbproject/private
+/.settings/
+/.classpath
+/.project
+/output.txt
+/lib/

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/README.txt
----------------------------------------------------------------------
diff --git a/geode-jvsd/README.txt b/geode-jvsd/README.txt
new file mode 100644
index 0000000..7ff9d63
--- /dev/null
+++ b/geode-jvsd/README.txt
@@ -0,0 +1,66 @@
+Check Java JDK version, it should version 1.8.0_60 or later.
+
+$java --version
+java version "1.8.0_60"
+Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
+Java HotSpot(TM) Server VM (build 25.60-b23, mixed mode)
+
+$jps
+13760 Jps
+ 
+Clone Geode from the Apache repository, checkout JVSD branch and then build Geode. 
+ 
+$git clone https://git-wip-us.apache.org/repos/asf/incubator-geode.git
+$cd incubator-geode/
+$git branch jvsd origin/feature/GEODE-78
+$git checkout jvsd
+$./gradlew clean build installDist -Dskip.tests=true
+
+Clone and build the third party charting library MultiAxisChartFX.
+$cd ..
+$git clone https://github.com/gemfire/MultiAxisChartFX
+$cd MultiAxisChartFX/
+$mvn install
+
+Install the MultiAxisChart jar into local maven repository.
+
+$mvn install:install-file \
+-Dfile=./MultiAxisChart-1.0-SNAPSHOT.jar \
+-DgroupId=com.pivotal.javafx \
+-DartifactId=MultiAxisChart \
+-Dversion=1.0-SNAPSHOT \
+-Dpackaging=jar \
+-DgeneratePom=true
+
+For testing, additional jars may need to be added from the Apache Geode build to
+the local Maven repository. 
+
+mvn install:install-file -Dfile=gemfire-core-1.0.0-incubating-SNAPSHOT.jar \
+-DgroupId=org.apache.geode \
+-DartifactId=gemfire-core \
+-Dversion=1.0.0-incubating-SNAPSHOT \
+-Dpackaging=jar \
+-DgeneratePom=true
+
+mvn install:install-file -Dfile=gemfire-jgroups-1.0.0-incubating-SNAPSHOT.jar \
+-DgroupId=org.apache.geode \
+-DartifactId=gemfire-jgroups \
+-Dversion=1.0.0-incubating-SNAPSHOT \
+-Dpackaging=jar \
+-DgeneratePom=true
+
+mvn install:install-file -Dfile=fastutil-7.0.2.jar \
+-DgroupId=org.apache.geode \
+-DartifactId=fastutil \
+-Dversion=7.0.2 \
+-Dpackaging=jar \
+-DgeneratePom=true
+
+Change back into the Geode directory and then into the JVSD directory. Build JVSD.
+$cd ../incubator-geode
+$cd geode-jvsd/
+$mvn install
+
+Run the JVSD application.
+$runjvsd.sh
+

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/pom.xml
----------------------------------------------------------------------
diff --git a/geode-jvsd/pom.xml b/geode-jvsd/pom.xml
new file mode 100644
index 0000000..8e1fdd5
--- /dev/null
+++ b/geode-jvsd/pom.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.apache.geode</groupId>
+    <artifactId>geode-jvsd</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>commons-cli</groupId>
+            <artifactId>commons-cli</artifactId>
+            <version>1.2</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-collections</groupId>
+            <artifactId>commons-collections</artifactId>
+            <version>3.2.1</version>
+        </dependency>
+        <!-- Enable only for Java 1.7 support
+        <dependency>
+                <groupId>com.oracle</groupId>
+                <artifactId>javafx</artifactId>
+                <version>2.2.60</version>
+                <scope>system</scope>
+                <systemPath>${java.home}/lib/jfxrt.jar</systemPath>
+        </dependency>
+        -->
+        <dependency>
+            <groupId>com.pivotal.javafx</groupId>
+            <artifactId>MultiAxisChart</artifactId>
+            <version>1.0-SNAPSHOT</version>
+            <!-- use scope and system path only if not installing jar into local maven repo -->
+            <!--<scope>system</scope>
+            <systemPath>${HOME}/Projects/MultiAxisChartFX/target/MultiAxisChart-1.0-SNAPSHOT.jar</systemPath> -->
+        </dependency>
+        <dependency>
+            <groupId>org.apache.geode</groupId>
+            <artifactId>gemfire-core</artifactId>
+            <version>1.0.0-incubating-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.geode</groupId>
+            <artifactId>gemfire-jgroups</artifactId>
+            <version>1.0.0-incubating-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.10</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <version>2.1</version>
+            <!--<scope>test</scope> -->
+            <type>pom</type> 
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-api</artifactId>
+            <version>2.1</version>
+            <!--<scope>test</scope> -->
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-core</artifactId>
+            <version>2.1</version>
+            <!--<scope>test</scope> -->
+        </dependency>
+        <dependency>
+            <groupId>org.apache.geode</groupId>
+            <artifactId>fastutil</artifactId>
+            <version>7.0.2</version>
+            <type>jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.transaction</groupId>
+            <artifactId>jta</artifactId>
+            <version>1.1</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>geode-jvsd</finalName>
+
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>1.3.1</version>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <transformers>
+                                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
+                            </transformers>
+                            <!-- <artifactSet> <includes> <include>org.mortbay.jetty:jetty*</include> 
+                            </includes> </artifactSet> -->
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.apache.geode.jvsd.fx.Main</mainClass>
+                        </manifest>
+                    </archive>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>2.5.1</version>
+                <configuration>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/runjvsd.sh
----------------------------------------------------------------------
diff --git a/geode-jvsd/runjvsd.sh b/geode-jvsd/runjvsd.sh
new file mode 100755
index 0000000..41f869c
--- /dev/null
+++ b/geode-jvsd/runjvsd.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+echo `which java`
+
+if [ -f target/geode-jvsd-jar-with-dependencies.jar ]; then
+    java -verbose:class -jar target/geode-jvsd-jar-with-dependencies.jar $@ >output.txt
+else
+    echo "missing critical jar file, rebuild"
+fi

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/src/META-INF/services/org.jdesktop.application.Application
----------------------------------------------------------------------
diff --git a/geode-jvsd/src/META-INF/services/org.jdesktop.application.Application b/geode-jvsd/src/META-INF/services/org.jdesktop.application.Application
new file mode 100644
index 0000000..c2e1341
--- /dev/null
+++ b/geode-jvsd/src/META-INF/services/org.jdesktop.application.Application
@@ -0,0 +1 @@
+visualdisplaytool.VisualDisplayToolApp
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/src/main/java/org/apache/geode/jvsd/chart/AdvancedLineChart.java
----------------------------------------------------------------------
diff --git a/geode-jvsd/src/main/java/org/apache/geode/jvsd/chart/AdvancedLineChart.java b/geode-jvsd/src/main/java/org/apache/geode/jvsd/chart/AdvancedLineChart.java
new file mode 100644
index 0000000..6d2da1f
--- /dev/null
+++ b/geode-jvsd/src/main/java/org/apache/geode/jvsd/chart/AdvancedLineChart.java
@@ -0,0 +1,703 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.jvsd.chart;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javafx.animation.KeyFrame;
+import javafx.animation.KeyValue;
+import javafx.animation.Timeline;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ObjectPropertyBase;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleDoubleProperty;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.event.Event;
+import javafx.event.EventHandler;
+import javafx.event.EventType;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Cursor;
+import javafx.scene.Node;
+import javafx.scene.chart.Axis;
+import javafx.scene.chart.ValueAxis;
+import javafx.scene.input.MouseButton;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.input.ScrollEvent;
+import javafx.scene.layout.Region;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Line;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.shape.StrokeType;
+import javafx.util.Duration;
+
+import com.pivotal.javafx.scene.chart.LegendItem;
+import com.pivotal.javafx.scene.chart.MultiAxisLineChart;
+import com.pivotal.javafx.scene.chart.Series;
+
+/**
+ * Line chart with crosshairs, zooming and panning.
+ * 
+ * @author jbarrett
+ * 
+ * @param <X>
+ * @param <Y>
+ */
+public class AdvancedLineChart<X extends Number, Y extends Number> extends MultiAxisLineChart<X, Y> {
+
+  /** Selected series */
+  private final ObjectProperty<Series<X, Y>> selectedSeries = new ObjectPropertyBase<Series<X, Y>>() {
+    {
+      addListener(new ChangeListener<Series<X, Y>>() {
+        @Override
+        public void changed(ObservableValue<? extends Series<X, Y>> observable, Series<X, Y> oldValue, Series<X, Y> newValue) {
+          if (null != oldValue) {
+            oldValue.getNode().getStyleClass().remove("advanced-chart-selected-series");
+            final LegendItem legendItem = oldValue.getLegendItem();
+            legendItem.getLabel().getStyleClass().remove("advanced-chart-selected-series");
+          }
+          if (null != newValue) {
+            newValue.getNode().toFront();
+            newValue.getNode().getStyleClass().add("advanced-chart-selected-series");
+            final LegendItem legendItem = newValue.getLegendItem();
+            legendItem.getLabel().getStyleClass().add("advanced-chart-selected-series");
+          }
+          requestChartLayout();
+        }
+      });
+    }
+
+    @Override
+    public Object getBean() {
+      return AdvancedLineChart.this;
+    }
+
+    @Override
+    public String getName() {
+      return "selectedSeries";
+    }
+  };
+
+  public final Series<X, Y> getSelectedSeries() {
+    return selectedSeries.get();
+  }
+
+  public final void setSelectedSeries(Series<X, Y> value) {
+    selectedSeries.set(value);
+  }
+
+  public final ObjectProperty<Series<X, Y>> selectedSeriesProperty() {
+    return selectedSeries;
+  }
+
+  private final Node chart;
+
+  // BEGIN Crosshair properties
+  private final DoubleProperty crosshairX = new SimpleDoubleProperty();
+  private final DoubleProperty crosshairY = new SimpleDoubleProperty();
+  final Line crosshairLineX;
+  final Line crosshairLineY;
+  // END Crosshair properties
+
+  // BEGIN Panning properties
+  private double panningLastX;
+  private double panningLastY;
+  private final SimpleBooleanProperty panning = new SimpleBooleanProperty(false);
+  // END Panning properties
+
+  // BEGIN Zooming properties
+  private final SimpleDoubleProperty rectX = new SimpleDoubleProperty();
+  private final SimpleDoubleProperty rectY = new SimpleDoubleProperty();
+  private final SimpleBooleanProperty zooming = new SimpleBooleanProperty(false);
+
+  private final DoubleProperty zoomDurationMillis = new SimpleDoubleProperty(700.0);
+  private final BooleanProperty zoomAnimated = new SimpleBooleanProperty(true);
+  private final BooleanProperty mouseWheelZoomAllowed = new SimpleBooleanProperty(true);
+
+  private static enum ZoomMode {
+    Horizontal, Vertical, Both
+  }
+
+  private ZoomMode zoomMode;
+
+  private final Rectangle selectRect;
+  private final Timeline zoomAnimation = new Timeline();
+  // END Zooming properties
+
+  public AdvancedLineChart(Axis<X> xAxis, Axis<Y> yAxis) {
+    super(xAxis, yAxis);
+
+    // Get the chart region
+    chart = getChartChildren().get(0).getParent();
+
+    // TODO style
+
+    crosshairLineX = new Line(0, 0, this.getWidth(), 0);
+    crosshairLineX.endXProperty().bind(this.widthProperty());
+    crosshairLineX.setStroke(Color.ORANGE);
+    crosshairLineX.setCursor(Cursor.CROSSHAIR);
+    crosshairLineX.setMouseTransparent(true);
+    crosshairLineX.translateYProperty().bind(crosshairY);
+    getPlotChildren().add(crosshairLineX);
+
+    crosshairLineY = new Line(0, 0, 0, this.getHeight());
+    crosshairLineY.endYProperty().bind(this.heightProperty());
+    crosshairLineY.setStroke(Color.ORANGE);
+    crosshairLineY.setCursor(Cursor.CROSSHAIR);
+    crosshairLineY.setMouseTransparent(true);
+    crosshairLineY.translateXProperty().bind(crosshairX);
+    getPlotChildren().add(crosshairLineY);
+
+    selectRect = new Rectangle(0, 0, 0, 0);
+    selectRect.setFill(Color.DODGERBLUE);
+    selectRect.setMouseTransparent(true);
+    selectRect.setOpacity(0.3);
+    selectRect.setStroke(Color.rgb(0, 0x29, 0x66));
+    selectRect.setStrokeType(StrokeType.INSIDE);
+    selectRect.setStrokeWidth(3.0);
+    selectRect.widthProperty().bind(rectX.subtract(selectRect.translateXProperty()));
+    selectRect.heightProperty().bind(rectY.subtract(selectRect.translateYProperty()));
+    selectRect.visibleProperty().bind(zooming);
+    getPlotChildren().add(selectRect);
+
+    chart.addEventHandler(MouseEvent.MOUSE_MOVED, new EventHandler<MouseEvent>() {
+      @Override
+      public void handle(MouseEvent event) {
+        // TODO change events on transforms?
+        // TODO bind to transform property because axis change
+        final double x = event.getX() - getXShift(getXAxis());
+        final double y = event.getY() - getYShift(getPrimaryYAxis());
+
+        // TODO do as properties? Crosshair.visible property?
+        if (y > 0 && y < getPrimaryYAxis().getHeight() && x > 0 && x < getXAxis().getWidth()) {
+          crosshairX.set(x);
+          crosshairY.set(y);
+          setCursor(Cursor.CROSSHAIR);
+          crosshairLineX.setVisible(true);
+          crosshairLineY.setVisible(true);
+        } else {
+          setCursor(Cursor.DEFAULT);
+          crosshairLineX.setVisible(false);
+          crosshairLineY.setVisible(false);
+        }
+      }
+    });
+
+    chart.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
+      @Override
+      public void handle(MouseEvent mouseEvent) {
+        if (isZoomEvent(mouseEvent))
+          setupZooming(mouseEvent.getX(), mouseEvent.getY());
+        mouseEvent.consume();
+      }
+    });
+
+    chart.addEventHandler(MouseEvent.DRAG_DETECTED, new EventHandler<MouseEvent>() {
+      @Override
+      public void handle(MouseEvent mouseEvent) {
+        if (isPanEvent(mouseEvent)) {
+          startPanning(mouseEvent.getX(), mouseEvent.getY());
+          mouseEvent.consume();
+        } else if (isZoomEvent(mouseEvent)) {
+          startZooming();
+          mouseEvent.consume();
+        }
+      }
+    });
+
+    chart.addEventHandler(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() {
+      @Override
+      public void handle(MouseEvent mouseEvent) {
+        if (panning.get()) {
+          doPanning(mouseEvent.getX(), mouseEvent.getY());
+          mouseEvent.consume();
+        } else if (zooming.get()) {
+          doZomming(mouseEvent.getX(), mouseEvent.getY());
+          mouseEvent.consume();
+        }
+      }
+    });
+
+    chart.addEventHandler(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() {
+      @Override
+      public void handle(MouseEvent mouseEvent) {
+        if (panning.get()) {
+          stopPanning();
+          mouseEvent.consume();
+        } else if (zooming.get()) {
+          stopZooming();
+          mouseEvent.consume();
+        }
+      }
+    });
+
+    chart.addEventHandler(ScrollEvent.ANY, new MouseWheelZoomHandler());
+
+  }
+
+  public final double getCrosshairX() {
+    return crosshairX.getValue();
+  }
+
+  public final DoubleProperty crosshairXProperty() {
+    return crosshairX;
+  }
+
+  public final double getCrosshairY() {
+    return crosshairY.getValue();
+  }
+
+  public final DoubleProperty crosshairYProperty() {
+    return crosshairY;
+  }
+
+  @Override
+  protected void seriesAdded(final Series<X, Y> series, int seriesIndex) {
+    super.seriesAdded(series, seriesIndex);
+
+    series.getNode().addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
+      @Override
+      public void handle(MouseEvent event) {
+        setSelectedSeries(getSelectedSeries() == series ? null : series);
+      }
+    });
+  }
+
+  @Override
+  protected void updateLegend() {
+    super.updateLegend();
+
+    for (final Series<X, Y> series : getData()) {
+      series.getLegendItem().getLabel().addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
+        @Override
+        public void handle(MouseEvent event) {
+          setSelectedSeries(getSelectedSeries() == series ? null : series);
+        }
+      });
+    }
+  }
+
+  @Override
+  protected void yAxisAdded(Axis<Y> yAxis) {
+    super.yAxisAdded(yAxis);
+
+    yAxis.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
+      @Override
+      public void handle(MouseEvent event) {
+        @SuppressWarnings("unchecked")
+        final Axis<Y> yAxis = (Axis<Y>) event.getSource();
+        setPrimaryYAxis(yAxis);
+      }
+    });
+  }
+
+  @Override
+  protected void layoutPlotChildren() {
+    super.layoutPlotChildren();
+    
+    selectRect.toFront();
+    crosshairLineX.toFront();
+    crosshairLineY.toFront();
+  }
+  
+  protected boolean isPanEvent(MouseEvent mouseEvent) {
+    return mouseEvent.getButton() == MouseButton.SECONDARY || (mouseEvent.getButton() == MouseButton.PRIMARY && mouseEvent.isShortcutDown());
+  }
+
+  protected boolean isZoomEvent(MouseEvent mouseEvent) {
+    return mouseEvent.getButton() == MouseButton.PRIMARY;
+  }
+
+  protected void startPanning(final double fromX, final double fromY) {
+    panningLastX = fromX;
+    panningLastY = fromY;
+
+    final Axis<X> xAxis = this.getXAxis();
+
+    // TODO check or bind to chart animation?
+    // wasXAnimated = xAxis.getAnimated();
+    // wasYAnimated = chart.getPrimaryYAxis().getAnimated();
+
+    xAxis.setAnimated(false);
+    xAxis.setAutoRanging(false);
+
+    for (Axis<Y> yAxis : getYAxes()) {
+      yAxis.setAnimated(false);
+      yAxis.setAutoRanging(false);
+    }
+
+    // TODO property?
+    panning.set(true);
+  }
+
+  protected void doPanning(final double toX, final double toY) {
+    // TODO animated?
+    // TODO do we really need to check this? Filters?
+    if (!panning.get()) {
+      return;
+    }
+
+    final ValueAxis<?> xAxis = (ValueAxis<?>) getXAxis();
+
+    final double dX = (toX - panningLastX) / -xAxis.getScale();
+    xAxis.setAutoRanging(false);
+    xAxis.setLowerBound(xAxis.getLowerBound() + dX);
+    xAxis.setUpperBound(xAxis.getUpperBound() + dX);
+
+    @SuppressWarnings("unchecked")
+    final List<ValueAxis<?>> yAxes = ((List<ValueAxis<?>>) (List<?>) getYAxes());
+    for (ValueAxis<?> yAxis : yAxes) {
+      final double dY = (toY - panningLastY) / -yAxis.getScale();
+      yAxis.setAutoRanging(false);
+      yAxis.setLowerBound(yAxis.getLowerBound() + dY);
+      yAxis.setUpperBound(yAxis.getUpperBound() + dY);
+    }
+
+    panningLastX = toX;
+    panningLastY = toY;
+  }
+
+  protected void stopPanning() {
+    if (!panning.get())
+      return;
+
+    panning.set(false);
+
+    // TODO should we allow certain axis to not be animated?
+    getXAxis().setAnimated(getAnimated());
+
+    for (Axis<Y> yAxis : getYAxes()) {
+      yAxis.setAnimated(getAnimated());
+    }
+  }
+
+  protected void setupZooming(final double x, final double y) {
+    final Axis<X> xAxis = getXAxis();
+    final Axis<Y> yAxis = getPrimaryYAxis();
+    
+    final double xShifted = x - getXShift(xAxis);
+    final double yShifted = y - getYShift(yAxis);
+
+    final double width = xAxis.getWidth();
+    final double height = yAxis.getHeight();
+    
+    if (xShifted >= 0 && xShifted <= width &&
+        yShifted >= 0 && yShifted <= height) {
+      selectRect.setTranslateX(xShifted);
+      selectRect.setTranslateY(yShifted);
+      rectX.set(xShifted);
+      rectY.set(yShifted);
+      zoomMode = ZoomMode.Both;
+
+    } else if (getComponentArea(xAxis).contains(x, y)) {
+      selectRect.setTranslateX(xShifted);
+      selectRect.setTranslateY(0);
+      rectX.set(xShifted);
+      rectY.set(height);
+      zoomMode = ZoomMode.Horizontal;
+
+    } else if (getComponentArea(yAxis).contains(x, y)) {
+      selectRect.setTranslateX(0);
+      selectRect.setTranslateY(yShifted);
+      rectX.set(width);
+      rectY.set(yShifted);
+      zoomMode = ZoomMode.Vertical;
+    }
+  }
+
+  protected void startZooming() {
+    // Don't actually start the selecting process until it's officially a drag
+    // But, we saved the original coordinates from where we started.
+    // TODO this??
+    zooming.set(true);
+  }
+
+  protected void doZomming(double x, double y) {
+    if (!zooming.get())
+      return;
+
+    final Axis<X> xAxis = getXAxis();
+    final Axis<Y> yAxis = getPrimaryYAxis();
+    
+    x -= getXShift(xAxis);
+    y -= getYShift(yAxis);
+    
+    if (zoomMode == ZoomMode.Both || zoomMode == ZoomMode.Horizontal) {
+      // Clamp to the selection start
+      x = Math.max(x, selectRect.getTranslateX());
+      // Clamp to plot area
+      x = Math.min(x, xAxis.getWidth());
+      rectX.set(x);
+    }
+
+    if (zoomMode == ZoomMode.Both || zoomMode == ZoomMode.Vertical) {
+      // Clamp to the selection start
+      y = Math.max(y, selectRect.getTranslateY());
+      // Clamp to plot area
+      y = Math.min(y, yAxis.getHeight());
+      rectY.set(y);
+    }
+  }
+
+  protected void stopZooming() {
+    if (!zooming.get())
+      return;
+
+    // Prevent a silly zoom... I'm still undecided about && vs ||
+    if (selectRect.getWidth() == 0.0 || selectRect.getHeight() == 0.0) {
+      zooming.set(false);
+      return;
+    }
+
+    // Rectangle2D zoomWindow = chartInfo.getDataCoordinates(
+    // selectRect.getTranslateX(), selectRect.getTranslateY(),
+    // rectX.get(), rectY.get()
+    // );
+
+    double minX = selectRect.getTranslateX();
+    double minY = selectRect.getTranslateY();
+    double maxX = rectX.get();
+    double maxY = rectY.get();
+
+    // TODO allow zoom in any x/y direction
+
+    if (minX > maxX || minY > maxY) {
+      throw new IllegalArgumentException("min > max for X and/or Y");
+    }
+
+    final ValueAxis<X> xAxis = (ValueAxis<X>) getXAxis();
+
+    // Axis xAxis = chart.getXAxis();
+    // Axis yAxis = chart.getPrimaryYAxis();
+
+    double minDataX = translatePositionToData(xAxis, minX);
+    double maxDataX = translatePositionToData(xAxis, maxX);
+
+    // The "low" Y data value is actually at the maxY graphical location as Y
+    // graphical axis gets
+    // larger as you go down on the screen.
+    // double minDataY = yAxis.toNumericValue( yAxis.getValueForDisplay( maxY -
+    // yStart ) );
+    // double maxDataY = yAxis.toNumericValue( yAxis.getValueForDisplay( minY -
+    // yStart ) );
+    //
+    // return new Rectangle2D( minDataX,
+    // minDataY,
+    // maxDataX - minDataX,
+    // maxDataY - minDataY );
+
+    if (zoomMode == ZoomMode.Both || zoomMode == ZoomMode.Horizontal) {
+      xAxis.setAutoRanging(false);
+    } else {
+      // TODO this doesn't work, try to auto range x when only zooming y??
+      ArrayList<X> d = new ArrayList<>();
+      getData().forEach(s -> {s.getData().forEach(dy -> {d.add(dy.getXValue());});});
+      xAxis.invalidateRange(d);
+    }
+
+    @SuppressWarnings("unchecked")
+    final List<ValueAxis<Y>> yAxes = ((List<ValueAxis<Y>>) (List<?>) getYAxes());
+    if (zoomMode == ZoomMode.Both || zoomMode == ZoomMode.Vertical) {
+      for (ValueAxis<Y> yAxis : yAxes) {
+        yAxis.setAutoRanging(false);
+      }
+    } else {
+      // TODO this doesn't work, try to auto range y when only zooming x
+      for (ValueAxis<Y> yAxis : yAxes) {
+      ArrayList<Y> d = new ArrayList<>();
+      getData().forEach(s -> {s.getData().forEach(dy -> {d.add(dy.getYValue());});});
+      yAxis.invalidateRange(d);
+      }
+    }
+    
+    if (zoomAnimated.get()) {
+      zoomAnimation.stop();
+
+      final KeyValue[] startFrames = new KeyValue[2 + (yAxes.size() * 2)];
+      final KeyValue[] stopFrames = new KeyValue[2 + (yAxes.size() * 2)];
+      int startFramesIndex = 0;
+      int stopFramesIndex = 0;
+      if (zoomMode == ZoomMode.Both || zoomMode == ZoomMode.Horizontal) {
+        startFrames[startFramesIndex++] = new KeyValue(xAxis.lowerBoundProperty(), xAxis.getLowerBound());
+        startFrames[startFramesIndex++] = new KeyValue(xAxis.upperBoundProperty(), xAxis.getUpperBound());
+        stopFrames[stopFramesIndex++] = new KeyValue(xAxis.lowerBoundProperty(), minDataX);
+        stopFrames[stopFramesIndex++] = new KeyValue(xAxis.upperBoundProperty(), maxDataX);
+      }
+      
+      if (zoomMode == ZoomMode.Both || zoomMode == ZoomMode.Vertical) {
+        for (ValueAxis<?> yAxis : yAxes) {
+          final double minDataY = translatePositionToData(yAxis, maxY);
+          final double maxDataY = translatePositionToData(yAxis, minY);
+          startFrames[startFramesIndex++] = new KeyValue(yAxis.lowerBoundProperty(), yAxis.getLowerBound());
+          startFrames[startFramesIndex++] = new KeyValue(yAxis.upperBoundProperty(), yAxis.getUpperBound());
+          stopFrames[stopFramesIndex++] = new KeyValue(yAxis.lowerBoundProperty(), minDataY);
+          stopFrames[stopFramesIndex++] = new KeyValue(yAxis.upperBoundProperty(), maxDataY);
+        }
+      }
+      
+      zoomAnimation.getKeyFrames().setAll(new KeyFrame(Duration.ZERO, startFrames), new KeyFrame(Duration.millis(zoomDurationMillis.get()), stopFrames));
+      zoomAnimation.play();
+    } else {
+      zoomAnimation.stop();
+      if (zoomMode == ZoomMode.Both || zoomMode == ZoomMode.Horizontal) {
+      xAxis.setLowerBound(minDataX);
+      xAxis.setUpperBound(maxDataX);
+      }
+      if (zoomMode == ZoomMode.Both || zoomMode == ZoomMode.Vertical) {
+        for (ValueAxis<?> yAxis : yAxes) {
+          final double minDataY = translatePositionToData(yAxis, maxY);
+          final double maxDataY = translatePositionToData(yAxis, minY);
+          yAxis.setLowerBound(minDataY);
+          yAxis.setUpperBound(maxDataY);
+        }
+      }
+    }
+
+    zooming.set(false);
+  }
+
+  protected Rectangle2D getComponentArea(Region childRegion) {
+    double xStart = getXShift(childRegion);
+    double yStart = getYShift(childRegion);
+
+    return new Rectangle2D(xStart, yStart, childRegion.getWidth(), childRegion.getHeight());
+  }
+
+  protected static final double getXShift(Node node) {
+    return node.getLocalToParentTransform().getTx();
+  }
+
+  protected static final double getYShift(Node node) {
+    return node.getLocalToParentTransform().getTy();
+  }
+
+  @SuppressWarnings({ "rawtypes", "unchecked" })
+  protected static final double translatePositionToData(ValueAxis axis, double position) {
+    return axis.toNumericValue(axis.getValueForDisplay(position));
+  }
+
+  protected class MouseWheelZoomHandler implements EventHandler<ScrollEvent> {
+    // TODO never getting start event
+    private boolean ignoring = false;
+
+    @Override
+    public void handle(ScrollEvent event) {
+      EventType<? extends Event> eventType = event.getEventType();
+      if (eventType == ScrollEvent.SCROLL_STARTED) {
+        // mouse wheel events never send SCROLL_STARTED, bullshit
+        ignoring = false;
+      } else if (eventType == ScrollEvent.SCROLL_FINISHED) {
+        // end non-mouse wheel event, never comes either
+        ignoring = false;
+
+      } else if (eventType == ScrollEvent.SCROLL &&
+      // If we are allowing mouse wheel zooming
+          mouseWheelZoomAllowed.get() &&
+          // If we aren't between SCROLL_STARTED and SCROLL_FINISHED
+          !ignoring &&
+          // inertia from non-wheel gestures might have touch count of 0
+          // !event.isInertia() &&
+          // Only care about vertical wheel events
+          event.getDeltaY() != 0 &&
+          // mouse wheel always has touch count of 0
+          event.getTouchCount() == 0) {
+
+        // If we are are doing a zoom animation, stop it. Also of note is that
+        // we don't zoom the
+        // mouse wheel zooming. Because the mouse wheel can "fly" and generate a
+        // lot of events,
+        // animation doesn't work well. Plus, as the mouse wheel changes the
+        // view a small amount in
+        // a predictable way, it "looks like" an animation when you roll it.
+        // We might experiment with mouse wheel zoom animation in the future,
+        // though.
+        zoomAnimation.stop();
+
+        final ValueAxis<?> xAxis = (ValueAxis<?>) getXAxis();
+
+        // If we wheel zoom on either axis, we restrict zooming to that axis
+        // only, else if anywhere
+        // else, including the plot area, zoom both axes.
+        ZoomMode zoomMode;
+        final double eventX = event.getX();
+        final double eventY = event.getY();
+        if (getComponentArea(xAxis).contains(eventX, eventY)) {
+          zoomMode = ZoomMode.Horizontal;
+        } else if (getComponentArea(getPrimaryYAxis()).contains(eventX, eventY)) {
+          // TODO other y axes
+          zoomMode = ZoomMode.Vertical;
+        } else {
+          zoomMode = ZoomMode.Both;
+        }
+
+        // At this point we are a mouse wheel event, based on everything I've
+        // read
+        // Point2D dataCoords = chartInfo.getDataCoordinates( eventX, eventY );
+
+        // TODO shift properties
+        final double xStart = getXShift(xAxis);
+        final double yStart = getYShift(getPrimaryYAxis());
+
+        double distance = event.getDeltaY();
+
+        // TODO accelerator property
+        double zoomAmount = 0.0005 * distance;
+
+        if (zoomMode == ZoomMode.Both || zoomMode == ZoomMode.Horizontal) {
+          // Determine the proportion of change to the lower and upper bounds
+          // based on how far the
+          // cursor is along the axis.
+          final double dataX = translatePositionToData(xAxis, eventX - xStart);
+          final double xZoomBalance = getBalance(dataX, xAxis.getLowerBound(), xAxis.getUpperBound());
+          final double xZoomDelta = (xAxis.getUpperBound() - xAxis.getLowerBound()) * zoomAmount;
+          xAxis.setAutoRanging(false);
+          xAxis.setLowerBound(xAxis.getLowerBound() - xZoomDelta * xZoomBalance);
+          xAxis.setUpperBound(xAxis.getUpperBound() + xZoomDelta * (1 - xZoomBalance));
+        }
+
+        if (zoomMode == ZoomMode.Both || zoomMode == ZoomMode.Vertical) {
+          @SuppressWarnings("unchecked")
+          final List<ValueAxis<?>> yAxes = ((List<ValueAxis<?>>) (List<?>) getYAxes());
+          for (final ValueAxis<?> yAxis : yAxes) {
+            // Determine the proportion of change to the lower and upper bounds
+            // based on how far the
+            // cursor is along the axis.
+            final double dataY = translatePositionToData(yAxis, eventY - yStart);
+            final double yZoomBalance = getBalance(dataY, yAxis.getLowerBound(), yAxis.getUpperBound());
+            final double yZoomDelta = (yAxis.getUpperBound() - yAxis.getLowerBound()) * zoomAmount;
+            yAxis.setAutoRanging(false);
+            yAxis.setLowerBound(yAxis.getLowerBound() - yZoomDelta * yZoomBalance);
+            yAxis.setUpperBound(yAxis.getUpperBound() + yZoomDelta * (1 - yZoomBalance));
+          }
+        }
+      }
+    }
+  }
+
+  protected static final double getBalance(double val, double min, double max) {
+    if (val <= min)
+      return 0.0;
+    else if (val >= max)
+      return 1.0;
+
+    return (val - min) / (max - min);
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/src/main/java/org/apache/geode/jvsd/chart/util/LargestTriangleThreeBuckets.java
----------------------------------------------------------------------
diff --git a/geode-jvsd/src/main/java/org/apache/geode/jvsd/chart/util/LargestTriangleThreeBuckets.java b/geode-jvsd/src/main/java/org/apache/geode/jvsd/chart/util/LargestTriangleThreeBuckets.java
new file mode 100644
index 0000000..2d1233e
--- /dev/null
+++ b/geode-jvsd/src/main/java/org/apache/geode/jvsd/chart/util/LargestTriangleThreeBuckets.java
@@ -0,0 +1,102 @@
+/* 
+ * Copied and ported from https://raw.githubusercontent.com/sveinn-steinarsson/flot-downsample/master/jquery.flot.downsample.js
+ * Portions of this file are under the following license:
+ * 
+ * The MIT License
+ * 
+ * Copyright (c) 2013 by Sveinn Steinarsson
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.geode.jvsd.chart.util;
+
+/**
+ * Java implementation of Largest Triangle Three Bucket downsampling algorithm
+ * based on http://skemman.is/stream/get/1946/15343/37285/3/SS_MSthesis.pdf.
+ * 
+ * @author jbarrett
+ * 
+ */
+public class LargestTriangleThreeBuckets {
+
+  public static final Number[][] downsample(final Number[][] data, final int threshold) {
+
+    final int dataLength = data.length;
+    if (threshold >= dataLength || 0 == threshold) {
+      return data; // Nothing to do
+    }
+
+    final Number[][] sampled = new Number[threshold][];
+    int sampledIndex = 0;
+
+    // Bucket size. Leave room for start and end data points
+    final double bucketSize = (double) (dataLength - 2) / (threshold - 2);
+
+    int a = 0; // Initially a is the first point in the triangle
+    int nextA = 0;
+
+    sampled[sampledIndex++] = data[a]; // Always add the first point
+
+    for (int i = 0; i < threshold - 2; i++) {
+
+      // Calculate point average for next bucket (containing c)
+      double pointCX = 0;
+      double pointCY = 0;
+      int pointCStart = (int) Math.floor((i + 1) * bucketSize) + 1;
+      int pointCEnd = (int) Math.floor((i + 2) * bucketSize) + 1;
+      pointCEnd = pointCEnd < dataLength ? pointCEnd : dataLength;
+      final int pointCSize = pointCEnd - pointCStart;
+      for (; pointCStart < pointCEnd; pointCStart++) {
+        pointCX += data[pointCStart][0].doubleValue(); // TODO DATE??
+        pointCY += data[pointCStart][1].doubleValue();
+      }
+      pointCX /= pointCSize;
+      pointCY /= pointCSize;
+
+      // Point a
+      double pointAX = data[a][0].doubleValue(); // TODO Date
+      double pointAY = data[a][1].doubleValue();
+
+      // Get the range for bucket b
+      int pointBStart = (int) Math.floor((i + 0) * bucketSize) + 1;
+      final int pointBEnd = (int) Math.floor((i + 1) * bucketSize) + 1;
+      double maxArea = -1;
+      Number[] maxAreaPoint = null;
+      for (; pointBStart < pointBEnd; pointBStart++) {
+        // Calculate triangle area over three buckets
+        final double area = Math.abs((pointAX - pointCX) * (data[pointBStart][1].doubleValue() - pointAY) - (pointAX - data[pointBStart][0].doubleValue())
+            * (pointCY - pointAY)) * 0.5;
+        if (area > maxArea) {
+          maxArea = area;
+          maxAreaPoint = data[pointBStart];
+          nextA = pointBStart; // Next a is this b
+        }
+      }
+
+      sampled[sampledIndex++] = maxAreaPoint; // Pick this point from the bucket
+      a = nextA; // This a is the next a (chosen b)
+    }
+
+    sampled[sampledIndex++] = data[dataLength - 1]; // Always add last
+
+    return sampled;
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/src/main/java/org/apache/geode/jvsd/controller/RootController.java
----------------------------------------------------------------------
diff --git a/geode-jvsd/src/main/java/org/apache/geode/jvsd/controller/RootController.java b/geode-jvsd/src/main/java/org/apache/geode/jvsd/controller/RootController.java
new file mode 100644
index 0000000..b0c6fb4
--- /dev/null
+++ b/geode-jvsd/src/main/java/org/apache/geode/jvsd/controller/RootController.java
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.jvsd.controller;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ResourceBundle;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.control.Button;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.ListView;
+import javafx.scene.control.SelectionMode;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableView;
+import javafx.scene.control.cell.PropertyValueFactory;
+import javafx.stage.FileChooser;
+
+import org.apache.geode.jvsd.fx.ChartManager;
+import org.apache.geode.jvsd.fx.StatFileManager;
+import org.apache.geode.jvsd.model.ResourceWrapper;
+import org.apache.geode.jvsd.model.stats.StatArchiveFile;
+import org.apache.geode.jvsd.model.stats.StatArchiveFile.StatValue;
+
+/**
+ * @author Jens Deppe
+ */
+public class RootController implements Initializable {
+
+  @FXML
+  private TableView<ResourceWrapper> tableView;
+
+  @FXML
+  private ListView listView;
+
+  @FXML
+  private Button newChartButton;
+
+  @FXML
+  private Button addToChartButton;
+
+  @FXML
+  private ComboBox chartCombo;
+
+  private ObservableList<Integer> chartList;
+
+  private ObservableList<ResourceWrapper> resources;
+
+  private ObservableList statNames;
+
+  private StatFileManager fileManager = StatFileManager.getInstance();
+
+  private ChartManager chartManager = ChartManager.getInstance();
+
+  @Override
+  public void initialize(URL url, ResourceBundle resourceBundle) {
+    double[] columnPercent = {
+        0.05,  // Row
+        0.25,  // Start Time
+        0.05,  // File
+        0.075, // Samples
+        0.075, // PID
+        0.2,   // Type
+        0.3    // Name
+    };
+
+    ObservableList<TableColumn<ResourceWrapper, ?>> cols = tableView.getColumns();
+    // Set column widths
+    for (int i = 0; i < 7; i++) {
+      cols.get(i).prefWidthProperty().
+          bind(tableView.widthProperty().multiply(columnPercent[i]));
+    }
+
+    cols.get(0).setCellValueFactory(new PropertyValueFactory<>("row"));
+    cols.get(1).setCellValueFactory(new PropertyValueFactory<>("startTime"));
+    cols.get(2).setCellValueFactory(new PropertyValueFactory<>("file"));
+    cols.get(3).setCellValueFactory(new PropertyValueFactory<>("samples"));
+    cols.get(4).setCellValueFactory(new PropertyValueFactory<>("pid"));
+    cols.get(5).setCellValueFactory(new PropertyValueFactory<>("type"));
+    cols.get(6).setCellValueFactory(new PropertyValueFactory<>("name"));
+
+    resources = FXCollections.observableArrayList();
+
+        updateArchives();
+
+    tableView.setItems(resources);
+    tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
+    tableView.getSelectionModel().selectedItemProperty().addListener(
+        (observableValue, oldValue, newValue) -> {
+          updateStatList();
+        });
+
+    statNames = FXCollections.observableArrayList();
+    listView.setItems(statNames);
+    listView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
+    listView.getSelectionModel().selectedItemProperty().addListener(
+        (observableValue, oldValue, newValue) -> {
+          System.out.println(newValue);
+        });
+
+    chartList = FXCollections.observableArrayList();
+    chartCombo.setItems(chartList);
+  }
+
+    private void updateArchives() {
+        int row = 0;
+        for (StatArchiveFile s : fileManager.getArchives()) {
+            for (StatArchiveFile.ResourceInst r : s.getResourceInstList()) {
+                resources.add(new ResourceWrapper(r, row++));
+            }
+        }
+    }
+
+  private List<StatValue> getSelectedStatValues() {
+    List<StatValue> result = new ArrayList<>();
+
+    List<String> statNames = listView.getSelectionModel().getSelectedItems();
+    ObservableList<ResourceWrapper> rows =
+        tableView.getSelectionModel().getSelectedItems();
+    if (rows != null && rows.size() > 0) {
+      for (ResourceWrapper r : rows) {
+        for (String stat : statNames) {
+          StatValue sv = r.getStatValue(stat);
+          result.add(sv);
+        }
+      }
+    }
+
+    return result;
+  }
+
+  @FXML
+  private void newChartButtonClicked() {
+    List<StatValue> svs = getSelectedStatValues();
+    if (svs.size() > 0) {
+      try {
+        int id = chartManager.newChart(svs.get(0));
+        svs.remove(0);
+        for (StatValue s : svs) {
+          chartManager.addToChart(id, s);
+        }
+        chartCombo.getSelectionModel().select(id);
+      } catch (IOException iox) {
+        iox.printStackTrace();
+      }
+    }
+  }
+
+  @FXML
+  private void addToChartButtonClicked() {
+    Integer i = (Integer) chartCombo.getSelectionModel().getSelectedItem();
+    List<StatValue> svs = getSelectedStatValues();
+    try {
+      for (StatValue s : svs) {
+        chartManager.addToChart(i, s);
+      }
+    } catch (IOException iox) {
+      iox.printStackTrace();
+    }
+  }
+
+  @FXML
+  private void handleOpenMenuAction(ActionEvent e){
+      try {
+          FileChooser fileChooser = new FileChooser();
+          fileChooser.setTitle("Open Statistics File");
+          File file=fileChooser.showOpenDialog(null);
+          StatFileManager.getInstance().add(file.getCanonicalPath());
+          updateArchives();
+      } catch (IOException ex) {
+          //TODO needs warning dialog of failed file open.
+      }
+  }
+  
+  @FXML
+  private void handleCloseMenuAction(ActionEvent e){
+    //TODO do a real clean up and shutdown   
+    //just kill it now
+      System.exit(0);
+  }
+  
+  public void setChartList(Collection<Integer> chartList) {
+    if (chartList.size() > 0) {
+      chartCombo.setDisable(false);
+      this.chartList.clear();
+      this.chartList.addAll(chartList);
+      this.chartCombo.getSelectionModel().select(0);
+      addToChartButton.setDisable(false);
+    } else {
+      this.chartList.clear();
+      chartCombo.setDisable(true);
+      addToChartButton.setDisable(true);
+    }
+
+  }
+
+  private void updateStatList() {
+    ObservableList<ResourceWrapper> rows =
+        tableView.getSelectionModel().getSelectedItems();
+    List<String> tmpStatNames = rows.get(0).getStatNames();
+
+    for (int i = 1; i < rows.size(); i++) {
+      List<String> tmpNames = rows.get(i).getStatNames();
+      Iterator<String> it = tmpStatNames.iterator();
+      while (it.hasNext()) {
+        String s = it.next();
+        if (!tmpNames.contains(s)) {
+          it.remove();
+        }
+      }
+    }
+
+    statNames.clear();
+    statNames.addAll(tmpStatNames);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/ChartManager.java
----------------------------------------------------------------------
diff --git a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/ChartManager.java b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/ChartManager.java
new file mode 100644
index 0000000..e5d10d0
--- /dev/null
+++ b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/ChartManager.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.jvsd.fx;
+
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.layout.Pane;
+import javafx.stage.Stage;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.geode.jvsd.controller.RootController;
+import org.apache.geode.jvsd.model.stats.StatArchiveFile;
+
+/**
+ * Basic container for all charts which are currently being displayed
+ *
+ * @author Jens Deppe
+ */
+public class ChartManager {
+
+  static final boolean MMTEST = true;
+
+  private Map<Integer, VSDChartWindow> charts = new HashMap<>();
+
+  private static ChartManager instance = new ChartManager();
+
+  private RootController root;
+
+  private int chartId = 0;
+
+  private ChartManager() {
+    // We are a singleton
+  }
+
+  public static ChartManager getInstance() {
+    return instance;
+  }
+
+  public VSDChartWindow add(VSDChartWindow chart) {
+    return chart;
+  }
+
+  public VSDChartWindow get(String chartName) {
+    return charts.get(chartName);
+  }
+
+  public void setRootController(RootController root) {
+    this.root = root;
+  }
+
+  public void removeChart(int id) {
+    charts.remove(id);
+    root.setChartList(charts.keySet());
+  }
+
+  public int newChart(StatArchiveFile.StatValue sv) throws IOException {
+    int id = chartId++;
+
+    FXMLLoader loader = new FXMLLoader(getClass().getResource("/chart.fxml"));
+    Pane chartPane = loader.load();
+    Stage stage = new Stage();
+    Scene scene = new Scene(chartPane);
+    scene.getStylesheets().add("META-INF/css/style.css");
+    stage.setScene(scene);
+    stage.show();
+
+    VSDChartWindow controller = loader.getController();
+    controller.setId(id);
+    charts.put(id, controller);
+    root.setChartList(charts.keySet());
+
+    controller.setStage(stage);
+//    if (!MMTEST) {
+      controller.addToChart(sv);
+//    }
+
+    return id;
+  }
+
+  public void addToChart(int id, StatArchiveFile.StatValue sv) throws IOException {
+    charts.get(id).addToChart(sv);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/HoveredThresholdNode.java
----------------------------------------------------------------------
diff --git a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/HoveredThresholdNode.java b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/HoveredThresholdNode.java
new file mode 100644
index 0000000..dab61c3
--- /dev/null
+++ b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/HoveredThresholdNode.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.jvsd.fx;
+
+import javafx.scene.Cursor;
+import javafx.scene.control.Label;
+import javafx.scene.layout.StackPane;
+
+public class HoveredThresholdNode extends StackPane {
+  public HoveredThresholdNode(String labelString) {
+    setPrefSize(2, 2);
+
+    final Label label = createDataThresholdLabel(labelString);
+
+    setOnMouseEntered(mouseEvent -> {
+      getChildren().setAll(label);
+      setCursor(Cursor.NONE);
+      toFront();
+    });
+    setOnMouseExited(mouseEvent -> {
+      getChildren().clear();
+      setCursor(Cursor.CROSSHAIR);
+    });
+  }
+
+  private Label createDataThresholdLabel(String labelString) {
+    final Label label = new Label(labelString);
+    // TODO style, colors, etc.
+    label.getStyleClass().addAll("chart-line-symbol", "chart-series-line");
+    label.setStyle("-fx-font-size: 8pt;");
+
+    label.setMinSize(Label.USE_PREF_SIZE, Label.USE_PREF_SIZE);
+    return label;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/LongDoubleMemoryMappedSeries.java
----------------------------------------------------------------------
diff --git a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/LongDoubleMemoryMappedSeries.java b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/LongDoubleMemoryMappedSeries.java
new file mode 100644
index 0000000..ee7be7b
--- /dev/null
+++ b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/LongDoubleMemoryMappedSeries.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.jvsd.fx;
+
+import java.nio.ByteBuffer;
+
+import org.apache.geode.jvsd.javafx.scene.chart.ByteBufferNumberSeries;
+
+import com.pivotal.javafx.scene.chart.Data;
+
+import javafx.collections.ObservableList;
+
+/**
+ * A named series of data items
+ */
+public class LongDoubleMemoryMappedSeries extends ByteBufferNumberSeries<Number, Number> {
+
+  // -------------- CONSTRUCTORS ----------------------------------------------
+
+  /**
+   * Construct a empty series
+   */
+  public LongDoubleMemoryMappedSeries() {
+    super();
+  }
+
+  /**
+   * Constructs a Series and populates it with the given {@link ObservableList}
+   * data.
+   *
+   * @param data
+   *          ObservableList of MultiAxisChart.Data
+   */
+  public LongDoubleMemoryMappedSeries(ByteBuffer buffer) {
+    super(buffer);
+  }
+
+  /**
+   * Constructs a named Series and populates it with the given
+   * {@link ObservableList} data.
+   *
+   * @param name
+   *          a name for the series
+   * @param data
+   *          ObservableList of MultiAxisChart.Data
+   */
+  public LongDoubleMemoryMappedSeries(String name, ByteBuffer buffer) {
+    super(name, buffer);
+  }
+
+  // BEGIN FORMAT SPECIFIC
+  private static final int DATA_X_OFFSET = 0;
+  private static final int DATA_X_LENGTH = 8;
+  private static final int DATA_Y_OFFSET = DATA_X_OFFSET + DATA_X_LENGTH;
+  private static final int DATA_Y_LENGTH = 8;
+  private static final int DATA_WIDTH = DATA_Y_OFFSET + DATA_Y_LENGTH;
+  
+  // TODO Make these Abstract
+  @Override
+  public int getDataSize() {
+    return getBuffer().limit() / DATA_WIDTH;
+  }
+  
+  // TODO Make these Abstract
+  private long getXRaw(final int index) {
+    return buffer.getLong(index * DATA_WIDTH + DATA_X_OFFSET);
+  }
+
+  // TODO Make these Abstract
+  private double getYRaw(final int index) {
+    return buffer.getDouble(index * DATA_WIDTH + DATA_Y_OFFSET);
+  }
+
+  // END FORMAT SPECIFIC
+ 
+  @Override
+  final protected Data<Number,Number> createData(int index) {
+    return new Data<>(getXRaw(index), getYRaw(index));
+  };
+    
+  @Override
+  final protected Long convertX(final double x) {
+    return (long) x;
+  }
+  
+  @Override
+  final protected Double convertY(final double y) {
+    return y;
+  }
+
+  @Override
+  final protected double getX(final int index) {
+    return (double) getXRaw(index);
+  };
+
+  @Override
+  final protected double getY(final int index) {
+    return (double) getYRaw(index);
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/Main.java
----------------------------------------------------------------------
diff --git a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/Main.java b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/Main.java
new file mode 100644
index 0000000..28f9af6
--- /dev/null
+++ b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/Main.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.jvsd.fx;
+
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.layout.Pane;
+import javafx.stage.Stage;
+
+import java.io.IOException;
+
+import org.apache.geode.jvsd.controller.RootController;
+
+/**
+ * @author Jens Deppe
+ */
+public class Main extends Application {
+
+  private Stage primaryStage;
+
+  private RootController controller;
+
+  @Override
+  public void start(Stage stage) throws IOException {
+    primaryStage = stage;
+    primaryStage.setTitle("jVSD");
+
+    FXMLLoader loader = new FXMLLoader(getClass().getResource("/jvsd.fxml"));
+    Pane rootLayout = loader.load();
+
+    Scene scene = new Scene(rootLayout);
+    scene.getStylesheets().add("/jvsd.css");
+
+    controller = loader.getController();
+
+    ChartManager.getInstance().setRootController(controller);
+
+    primaryStage.setScene(scene);
+    primaryStage.show();
+  }
+
+  public static void main(String[] args) throws IOException {
+    StatFileManager.getInstance().add(args);
+    launch(args);
+  } 
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/ObservableByteBuffer.java
----------------------------------------------------------------------
diff --git a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/ObservableByteBuffer.java b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/ObservableByteBuffer.java
new file mode 100644
index 0000000..a68db32
--- /dev/null
+++ b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/ObservableByteBuffer.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.jvsd.fx;
+
+public class ObservableByteBuffer {
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/ObservableDataBuffer.java
----------------------------------------------------------------------
diff --git a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/ObservableDataBuffer.java b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/ObservableDataBuffer.java
new file mode 100644
index 0000000..9f24715
--- /dev/null
+++ b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/ObservableDataBuffer.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.jvsd.fx;
+
+import javafx.collections.ObservableListBase;
+
+import com.pivotal.javafx.scene.chart.Data;
+
+public class ObservableDataBuffer<X, Y> extends ObservableListBase<Data<X, Y>> {
+
+  @Override
+  public Data<X, Y> get(int index) {
+    // TODO Auto-generated method stub
+    return null;
+  }
+  
+  @Override
+  public boolean add(Data<X, Y> e) {
+    
+    
+    return true;
+  }
+
+  @Override
+  public int size() {
+    // TODO Auto-generated method stub
+    return 0;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/StatFileManager.java
----------------------------------------------------------------------
diff --git a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/StatFileManager.java b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/StatFileManager.java
new file mode 100644
index 0000000..cae2be9
--- /dev/null
+++ b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/StatFileManager.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.jvsd.fx;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.geode.jvsd.model.stats.StatArchiveFile;
+
+/**
+ * Basic container for all stat files we are managing.
+ *
+ * @author Jens Deppe
+ */
+public class StatFileManager {
+
+  private List<StatArchiveFile> statFiles = new ArrayList<>();
+
+  private static StatFileManager instance = new StatFileManager();
+
+  private StatFileManager() {
+    // We are a singleton
+  }
+
+  public static StatFileManager getInstance() {
+    return instance;
+  }
+
+  public void add(String[] fileNames) throws IOException {
+    for (String name : fileNames) {
+      statFiles.add(new StatArchiveFile(name));
+    }
+  }
+  
+  public void add(String fileName) throws IOException {
+      statFiles.add(new StatArchiveFile(fileName));
+  }
+
+  public List<StatArchiveFile> getArchives() {
+    return statFiles;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/VSDChartWindow.java
----------------------------------------------------------------------
diff --git a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/VSDChartWindow.java b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/VSDChartWindow.java
new file mode 100644
index 0000000..40d0474
--- /dev/null
+++ b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/VSDChartWindow.java
@@ -0,0 +1,288 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.jvsd.fx;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.net.URL;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.text.DateFormat;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ResourceBundle;
+
+import javafx.beans.binding.Bindings;
+import javafx.collections.FXCollections;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.geometry.Side;
+import javafx.scene.chart.Axis;
+import javafx.scene.chart.NumberAxis;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.layout.Pane;
+import javafx.stage.Stage;
+
+import org.apache.geode.jvsd.chart.AdvancedLineChart;
+import org.apache.geode.jvsd.javafx.scene.chart.DateAxis;
+import org.apache.geode.jvsd.model.stats.StatArchiveFile.StatValue;
+import org.apache.geode.jvsd.stats.Utility;
+
+import com.pivotal.javafx.scene.chart.BasicSeries;
+import com.pivotal.javafx.scene.chart.Data;
+
+/**
+ * An advanced line chart with a variety of actions and settable properties.
+ *
+ * @see javafx.scene.chart.LineChart
+ * @see javafx.scene.chart.Chart
+ * @see javafx.scene.chart.NumberAxis
+ * @see javafx.scene.chart.XYChart
+ */
+public class VSDChartWindow implements Initializable {
+
+  private int chartId;
+
+  private Stage stage;
+
+  private AdvancedLineChart<Number, Number> chart;
+
+  private ChartManager manager = ChartManager.getInstance();
+
+  @FXML
+  private Pane root;
+
+  @FXML
+  private Button resetButton;
+
+  @FXML
+  private Label coordLabel;
+
+  public void setStage(Stage stage) {
+    this.stage = stage;
+    this.stage.setOnCloseRequest(eh -> {
+      manager.removeChart(chartId);
+    });
+  }
+
+  public void setId(int chartId) {
+    this.chartId = chartId;
+  }
+
+  @Override
+  public void initialize(URL url, ResourceBundle resourceBundle) {
+    chart = createChart(root);
+    root.getChildren().add(chart);
+
+    // TODO better way to do this?
+    coordLabel.textProperty().bind(Bindings.format("%s x %s",
+        Bindings.createStringBinding(() -> DateFormat.getInstance().format(
+                chart.getXAxis().getValueForDisplay(
+                    chart.crosshairXProperty().doubleValue())),
+            chart.crosshairXProperty()),
+        Bindings.createStringBinding(() -> {
+          final NumberFormat formatter = NumberFormat.getInstance();
+          formatter.setGroupingUsed(true);
+          return formatter.format(
+              chart.getPrimaryYAxis().getValueForDisplay(
+                  chart.crosshairYProperty().doubleValue()));
+        }, chart.crosshairYProperty(), chart.primaryYAxisProperty())));
+  }
+
+  @FXML
+  private void resetRangeClicked() {
+    chart.getXAxis().setAutoRanging(true);
+    for (Axis<Number> yAxis : chart.getYAxes()) {
+      yAxis.setAutoRanging(true);
+    }
+  }
+
+  protected AdvancedLineChart<Number, Number> createChart(Pane root) {
+    final DateAxis xAxis = new DateAxis();
+    xAxis.setAutoRanging(true);
+    final NumberAxis yAxis = new NumberAxis();
+    final AdvancedLineChart<Number, Number> lc =
+        new AdvancedLineChart<>(xAxis, yAxis);
+    lc.setCreateSymbols(false);
+    lc.prefWidthProperty().bind(root.widthProperty());
+    lc.prefHeightProperty().bind(root.heightProperty());
+
+    if (ChartManager.MMTEST) {
+//    
+//    try (final RandomAccessFile memoryMappedFile = new RandomAccessFile("/tmp/test.vsd.stat1.vss", "rw")) {
+//
+//      final MappedByteBuffer in = memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, memoryMappedFile.length());
+////      in.limit(1000000);
+//      final LongDoubleMemoryMappedSeries s = new LongDoubleMemoryMappedSeries(in);
+//      lc.getData().add(s);
+//
+////      Timeline animation = new Timeline();
+////      animation.getKeyFrames().add(new KeyFrame(Duration.seconds(1), actionEvent -> {
+////          in.limit(in.limit()+16);
+////          System.out.println(in.limit());
+//////          lc.toFront();
+//////          Data<Number, Number> last = s.getData().get(s.getData().size() - 1);
+//////          s.getData().add(new Data<>(last.getXValue().longValue() + 1000, last.getYValue().doubleValue() + (new Random().nextDouble() * 1000) - 500));
+////        
+////      }));
+////     animation.setCycleCount(Animation.INDEFINITE);
+////     animation.play();
+//      
+//
+//    } catch (FileNotFoundException e) {
+//      // TODO Auto-generated catch block
+//      e.printStackTrace();
+//    } catch (IOException e) {
+//      // TODO Auto-generated catch block
+//      e.printStackTrace();
+//    }
+    
+    }
+
+//  lc.getData().add(new LongDoubleFileSeries(new File("/tmp/test.vsd.stat1.vss")));
+
+    return lc;
+  }
+
+  public void addToChart(StatValue sv) {
+    if (!stage.titleProperty().isBound()) {
+      stage.setTitle(
+          "Chart" + chartId + "_" + sv.getDescriptor().getName());
+    }
+
+    Axis<Number> yAxis;
+    final String units = sv.getDescriptor().getUnits();
+    yAxis = findOrCreateYAxis(units);
+
+    final long[] timestamps = sv.getRawAbsoluteTimeStamps();
+    final double[] values = sv.getRawSnapshots();
+
+    long timeSampleAverage = Utility.computeTimeSampleAverage(timestamps);
+    long samplePeriod = timeSampleAverage / 1000;
+
+    final List<Data<Number, Number>> data; 
+    switch (sv.getFilter()) {
+    case StatValue.FILTER_NONE:
+      data = doFilterNone(samplePeriod, timestamps, values);
+      break;
+    case StatValue.FILTER_PERSEC:
+      data = doFilterPerSec(samplePeriod, timestamps, values);
+      break;
+    default:
+      data = null;
+    }
+
+    // add series data
+    if (ChartManager.MMTEST) {
+      addToChartMM(sv, yAxis, data);
+    } else {
+      BasicSeries<Number, Number> series = new BasicSeries<>(FXCollections.observableList(data));
+      series.setYAxis(yAxis);
+      series.setName(sv.getDescriptor().getName());
+      chart.getData().add(series);
+    }
+  }
+
+  private void addToChartMM(StatValue sv, Axis<Number> yAxis, List<Data<Number, Number>> data) {
+    try {
+      final File tmp = File.createTempFile(sv.getDescriptor().getName(), ".vss");
+      tmp.deleteOnExit();
+      
+      try (final RandomAccessFile memoryMappedFile = new RandomAccessFile(tmp, "rw")) {
+        final MappedByteBuffer out = memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, data.size() * 16);
+
+        for (Data<Number, Number> d : data) {
+          out.putLong(d.getXValue().longValue());
+          out.putDouble(d.getYValue().doubleValue());
+        }
+        out.force();
+
+        final LongDoubleMemoryMappedSeries s = new LongDoubleMemoryMappedSeries(out);
+        s.setYAxis(yAxis);
+        s.setName(sv.getDescriptor().getName());
+        chart.getData().add(s);
+      }
+    } catch (IOException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+  }
+
+  protected List<Data<Number, Number>> doFilterNone(long samplePeriod, final long[] timestamps, final double[] values) {
+    final ArrayList<Data<Number, Number>> list = new ArrayList<>(timestamps.length - 1);
+    
+    // skip first value to calc value/s.
+    for (int i = 1; i < timestamps.length; i++) {
+      final Data<Number, Number> data = new Data<Number, Number>(timestamps[i], values[i]);
+
+//TODO speed up      data.setNode(new HoveredThresholdNode(numberFormat.format(values[i])));
+      list.add(data);
+    }
+    
+    return list;
+  }
+
+  protected List<Data<Number, Number>> doFilterPerSec(long samplePeriod, final long[] timestamps, final double[] values) {
+    final ArrayList<Data<Number, Number>> list = new ArrayList<>(timestamps.length - 1);
+
+    // skip first value to calc value/s.
+    for (int i = 1; i < timestamps.length; i++) {
+      final long timestamp = timestamps[i];
+      final double psValue = Utility.computePerSecondValue(timestamp, timestamps[i - 1], values[i], values[i - 1], samplePeriod);
+      final Data<Number, Number> data = new Data<Number, Number>(timestamp, psValue);
+
+//TODO speed up      data.setNode(new HoveredThresholdNode(numberFormat.format(psValue)));
+      list.add(data);
+    }
+    
+    return list;
+  }
+
+  private Axis<Number> findOrCreateYAxis(final String units) {
+    Axis<Number> yAxis = findYAxis(units);
+
+    if (null == yAxis) {
+      yAxis = new NumberAxis();
+      yAxis.setSide(Side.RIGHT);
+      chart.getYAxes().add(yAxis);
+    }
+
+    if (null == yAxis.getLabel()) {
+      yAxis.setLabel(units);
+    }
+
+    return yAxis;
+  }
+
+  private Axis<Number> findYAxis(final String units) {
+    for (Axis<Number> y : chart.getYAxes()) {
+      if (null == y.getLabel() || y.getLabel().equals(units)) {
+        return y;
+      }
+    }
+
+    return null;
+  }
+
+
+  public String getTitle() {
+    return stage.getTitle();
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/8a86c01a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/VsdSeries.java
----------------------------------------------------------------------
diff --git a/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/VsdSeries.java b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/VsdSeries.java
new file mode 100644
index 0000000..25c8ef1
--- /dev/null
+++ b/geode-jvsd/src/main/java/org/apache/geode/jvsd/fx/VsdSeries.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.jvsd.fx;
+
+import java.nio.MappedByteBuffer;
+
+public class VsdSeries  {
+  // TODO concurrency
+  
+  private MappedByteBuffer buffer = null;
+  
+  private int length = 0;
+  
+  
+  
+  
+  public void addData(long time, double value) {
+    if (buffer.remaining() < 8) {
+      expandBuffer();
+    }
+    
+    buffer.putLong(time);
+    buffer.putDouble(value);
+    length++;
+    
+    // TODO notify change
+  }
+
+  public int getLength() {
+    return length;
+  }
+
+  private void expandBuffer() {
+    // TODO Auto-generated method stub
+    
+  }
+}