geode-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dschnei...@apache.org
Subject [7/7] incubator-geode git commit: GEODE-78: Imported jvsdfx-mm from geode-1.0.0-SNAPSHOT-2.src.tar
Date Mon, 06 Jul 2015 21:46:49 GMT
GEODE-78: Imported jvsdfx-mm from geode-1.0.0-SNAPSHOT-2.src.tar


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

Branch: refs/heads/feature/GEODE-78
Commit: e299ea844e2837a957b7847010f0a389fd08bff4
Parents: 11d0367
Author: Darrel Schneider <dschneider@pivotal.io>
Authored: Mon Jul 6 10:45:30 2015 -0700
Committer: Darrel Schneider <dschneider@pivotal.io>
Committed: Mon Jul 6 10:45:30 2015 -0700

----------------------------------------------------------------------
 jvsdfx-mm/.gitignore                            |    5 +
 jvsdfx-mm/REMOVED_JARS.txt                      |   11 +
 jvsdfx-mm/lib/nblibraries.properties            |    4 +
 jvsdfx-mm/pom.xml                               |   98 +
 .../org.jdesktop.application.Application        |    1 +
 .../com/pivotal/chart/AdvancedLineChart.java    |  688 ++++
 .../chart/util/LargestTriangleThreeBuckets.java |  102 +
 .../com/sun/javafx/charts/LegendItem.java       |   47 +
 .../javafx/scene/chart/AbstractSeries.java      |  269 ++
 .../javafx/scene/chart/BasicDataSet.java        |  134 +
 .../scene/chart/ByteBufferNumberSeries.java     |  432 +++
 .../javafx/scene/chart/DataChangedListener.java |    5 +
 .../com/pivotal/javafx/scene/chart/DataSet.java |   11 +
 .../pivotal/javafx/scene/chart/DateAxis.java    |  393 ++
 .../com/pivotal/javafx/scene/chart/Series.java  |   81 +
 .../main/java/com/pivotal/jvsd/AboutJVSD.form   |   69 +
 .../main/java/com/pivotal/jvsd/AboutJVSD.java   |  104 +
 .../pivotal/jvsd/ChartMenuActionListener.java   |   39 +
 .../java/com/pivotal/jvsd/ChartSidePanel.form   |  325 ++
 .../src/main/java/com/pivotal/jvsd/HUD.form     |  113 +
 .../src/main/java/com/pivotal/jvsd/HUD.java     |   99 +
 .../java/com/pivotal/jvsd/StatValueWrapper.java |   45 +
 .../com/pivotal/jvsd/StatsTableColumnModel.java |   95 +
 .../java/com/pivotal/jvsd/StatsTableModel.java  |   61 +
 .../com/pivotal/jvsd/VSDConnectionsDialog.form  |   35 +
 .../com/pivotal/jvsd/VSDConnectionsDialog.java  |   66 +
 .../java/com/pivotal/jvsd/VSDDataWindow.java    |    9 +
 .../java/com/pivotal/jvsd/VSDHelpWindow.form    |   77 +
 .../java/com/pivotal/jvsd/VSDHelpWindow.java    |   90 +
 .../java/com/pivotal/jvsd/VSDMainWindow.java    |  417 ++
 .../com/pivotal/jvsd/VSDPreferencesDialog.form  |   35 +
 .../com/pivotal/jvsd/VSDPreferencesDialog.java  |   66 +
 .../com/pivotal/jvsd/VSDStatInfoWindow.java     |    9 +
 .../java/com/pivotal/jvsd/VSDStatsPane.java     |   53 +
 .../java/com/pivotal/jvsd/VisualDisplayApp.java |   88 +
 .../pivotal/jvsd/controller/RootController.java |  195 +
 .../java/com/pivotal/jvsd/fx/ChartManager.java  |   83 +
 .../pivotal/jvsd/fx/HoveredThresholdNode.java   |   33 +
 .../jvsd/fx/LongDoubleMemoryMappedSeries.java   |   98 +
 .../src/main/java/com/pivotal/jvsd/fx/Main.java |   44 +
 .../pivotal/jvsd/fx/ObservableByteBuffer.java   |    5 +
 .../pivotal/jvsd/fx/ObservableDataBuffer.java   |   31 +
 .../com/pivotal/jvsd/fx/StatFileManager.java    |   37 +
 .../com/pivotal/jvsd/fx/VSDChartWindow.java     |  289 ++
 .../java/com/pivotal/jvsd/fx/VsdSeries.java     |   37 +
 .../com/pivotal/jvsd/model/ResourceWrapper.java |   57 +
 .../jvsd/model/stats/StatArchiveFile.java       | 3423 +++++++++++++++++
 .../jvsd/model/stats/StatArchiveFormat.java     |  190 +
 .../com/pivotal/jvsd/stats/GraphTemplates.java  |    9 +
 .../java/com/pivotal/jvsd/stats/IssueRules.java |    9 +
 .../pivotal/jvsd/stats/StatArchiveFormat.java   |  190 +
 .../com/pivotal/jvsd/stats/StatFileManager.java |  125 +
 .../com/pivotal/jvsd/stats/StatFileParser.java  | 3591 ++++++++++++++++++
 .../com/pivotal/jvsd/stats/StatFileWrapper.java |   93 +
 .../java/com/pivotal/jvsd/stats/Utility.java    |  229 ++
 .../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 +
 jvsdfx-mm/src/main/resources/chart.fxml         |   31 +
 jvsdfx-mm/src/main/resources/jvsd.css           |    3 +
 jvsdfx-mm/src/main/resources/jvsd.fxml          |   96 +
 .../java/io/pivotal/jvsd/CopyOfCopyOfTest.java  |   40 +
 .../test/java/io/pivotal/jvsd/CopyOfTest.java   |   45 +
 .../java/io/pivotal/jvsd/Copy_2_of_Test.java    |   61 +
 .../src/test/java/io/pivotal/jvsd/Test.java     |  110 +
 84 files changed, 13457 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/e299ea84/jvsdfx-mm/.gitignore
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/.gitignore b/jvsdfx-mm/.gitignore
new file mode 100644
index 0000000..3840662
--- /dev/null
+++ b/jvsdfx-mm/.gitignore
@@ -0,0 +1,5 @@
+target
+nbproject/private
+/.settings/
+/.classpath
+/.project

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/e299ea84/jvsdfx-mm/REMOVED_JARS.txt
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/REMOVED_JARS.txt b/jvsdfx-mm/REMOVED_JARS.txt
new file mode 100644
index 0000000..aa44411
--- /dev/null
+++ b/jvsdfx-mm/REMOVED_JARS.txt
@@ -0,0 +1,11 @@
+The following jars were removed from this source code. These should be
+downloaded from maven central as part of the build.
+
+lib/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar
+lib/commons-cli-1.2.jar
+lib/commons-collections-3.2.1.jar
+lib/commons-io-2.0.1.jar
+lib/commons-lang3-3.0.jar
+lib/commons-net-3.0.1.jar
+lib/jcommon-1.0.21.jar
+lib/jfreechart-1.0.17.jar

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/e299ea84/jvsdfx-mm/lib/nblibraries.properties
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/lib/nblibraries.properties b/jvsdfx-mm/lib/nblibraries.properties
new file mode 100644
index 0000000..6d0afb5
--- /dev/null
+++ b/jvsdfx-mm/lib/nblibraries.properties
@@ -0,0 +1,4 @@
+libs.CopyLibs.classpath=\
+    ${base}/CopyLibs/org-netbeans-modules-java-j2seproject-copylibstask.jar
+libs.CopyLibs.displayName=CopyLibs Task
+libs.CopyLibs.prop-version=2.0

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/e299ea84/jvsdfx-mm/pom.xml
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/pom.xml b/jvsdfx-mm/pom.xml
new file mode 100644
index 0000000..7268f96
--- /dev/null
+++ b/jvsdfx-mm/pom.xml
@@ -0,0 +1,98 @@
+<?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>com.pivotal</groupId>
+	<artifactId>jvsdfx</artifactId>
+	<version>1.0-SNAPSHOT</version>
+	<packaging>jar</packaging>
+
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<maven.compiler.source>1.8</maven.compiler.source>
+		<maven.compiler.target>1.8</maven.compiler.target>
+	</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>
+		-->
+	</dependencies>
+
+	<build>
+		<finalName>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>com.pivotal.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/e299ea84/jvsdfx-mm/src/META-INF/services/org.jdesktop.application.Application
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/src/META-INF/services/org.jdesktop.application.Application b/jvsdfx-mm/src/META-INF/services/org.jdesktop.application.Application
new file mode 100644
index 0000000..c2e1341
--- /dev/null
+++ b/jvsdfx-mm/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/e299ea84/jvsdfx-mm/src/main/java/com/pivotal/chart/AdvancedLineChart.java
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/src/main/java/com/pivotal/chart/AdvancedLineChart.java b/jvsdfx-mm/src/main/java/com/pivotal/chart/AdvancedLineChart.java
new file mode 100644
index 0000000..dda38a9
--- /dev/null
+++ b/jvsdfx-mm/src/main/java/com/pivotal/chart/AdvancedLineChart.java
@@ -0,0 +1,688 @@
+package com.pivotal.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.com.sun.javafx.charts.LegendItem;
+import com.pivotal.javafx.scene.chart.Series;
+import com.pivotal.javafx.scene.chart.MultiAxisLineChart;
+
+/**
+ * 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");
+    }
+
+    @SuppressWarnings("unchecked")
+    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/e299ea84/jvsdfx-mm/src/main/java/com/pivotal/chart/util/LargestTriangleThreeBuckets.java
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/src/main/java/com/pivotal/chart/util/LargestTriangleThreeBuckets.java b/jvsdfx-mm/src/main/java/com/pivotal/chart/util/LargestTriangleThreeBuckets.java
new file mode 100644
index 0000000..62931f0
--- /dev/null
+++ b/jvsdfx-mm/src/main/java/com/pivotal/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 com.pivotal.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/e299ea84/jvsdfx-mm/src/main/java/com/pivotal/com/sun/javafx/charts/LegendItem.java
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/src/main/java/com/pivotal/com/sun/javafx/charts/LegendItem.java b/jvsdfx-mm/src/main/java/com/pivotal/com/sun/javafx/charts/LegendItem.java
new file mode 100644
index 0000000..33961d6
--- /dev/null
+++ b/jvsdfx-mm/src/main/java/com/pivotal/com/sun/javafx/charts/LegendItem.java
@@ -0,0 +1,47 @@
+package com.pivotal.com.sun.javafx.charts;
+
+import java.lang.reflect.Field;
+
+import javafx.scene.Node;
+import javafx.scene.control.Label;
+
+import com.sun.javafx.charts.Legend;
+
+/**
+ * Extends {@link Legend.LegendItem} to expose label field as property.
+ * 
+ * @author jbarrett
+ *
+ */
+public class LegendItem extends Legend.LegendItem {
+
+  static final Field labelField;
+  static {
+    Field field = null;
+    try {
+      field = Legend.LegendItem.class.getDeclaredField("label");
+      field.setAccessible(true);
+    } catch (Exception e) {
+      throw new IllegalStateException(e);
+    }
+    labelField = field;
+  }
+
+  public LegendItem(String text) {
+    super(text);
+
+  }
+
+  public LegendItem(String text, Node symbol) {
+    super(text, symbol);
+  }
+  
+  public Label getLabel() {
+    try {
+      return (Label) labelField.get(this);
+    } catch (IllegalArgumentException | IllegalAccessException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/e299ea84/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/AbstractSeries.java
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/AbstractSeries.java b/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/AbstractSeries.java
new file mode 100644
index 0000000..ad43e76
--- /dev/null
+++ b/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/AbstractSeries.java
@@ -0,0 +1,269 @@
+package com.pivotal.javafx.scene.chart;
+
+import java.util.stream.Collectors;
+
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ReadOnlyObjectProperty;
+import javafx.beans.property.ReadOnlyObjectWrapper;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.property.StringProperty;
+import javafx.beans.property.StringPropertyBase;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.ObservableList;
+import javafx.scene.Node;
+import javafx.scene.chart.Axis;
+import javafx.scene.chart.ValueAxis;
+
+import com.pivotal.com.sun.javafx.charts.LegendItem;
+
+/**
+ * A named series of data items
+ */
+public abstract class AbstractSeries<X extends Number,Y extends Number> implements Series<X, Y> {
+
+    // -------------- PRIVATE PROPERTIES ----------------------------------------
+
+    /** the style class for default color for this series */
+    protected String defaultColorStyleClass;
+
+    // TODO make property
+    @Override
+    public String getDefaultColorStyleClass() {
+      return defaultColorStyleClass;
+    }
+    
+    @Override
+    public void setDefaultColorStyleClass(String defaultColorStyleClass) {
+      this.defaultColorStyleClass = defaultColorStyleClass;
+    }
+
+//    // TODO remove
+//    protected Data<X,Y> begin = null; // start pointer of a data linked list.
+//    
+//    // TODO remove
+//    @Override
+//    public Data<X, Y> getBegin() {
+//      return begin;
+//    }
+//
+//    // TODO remove
+//    @Override
+//    public void setBegin(Data<X, Y> begin) {
+//      this.begin = begin;
+//    }
+
+    /*
+     * Next pointer for the next series. We maintain a linkedlist of the
+     * serieses  so even after the series is deleted from the list,
+     * we have a reference to it - needed by BarChart e.g.
+     */
+    // TODO remove
+    protected Series<X,Y> next = null;
+
+    // TODO remove
+    @Override
+    public Series<X, Y> getNext() {
+      return next;
+    }
+
+    // TODO remove
+    @Override
+    public void setNext(Series<X, Y> next) {
+      this.next = next;
+    }
+
+
+    // -------------- PUBLIC PROPERTIES ----------------------------------------
+
+    /** Reference to the chart this series belongs to */
+    private final ReadOnlyObjectWrapper<MultiAxisChart<X, Y>> chart = new ReadOnlyObjectWrapper<MultiAxisChart<X, Y>>(this, "chart");
+
+    /* (non-Javadoc)
+     * @see com.pivotal.javafx.scene.chart.Series#getChart()
+     */
+    @Override
+    public final MultiAxisChart<X,Y> getChart() { return chart.get(); }
+    @Override
+    public void setChart(MultiAxisChart<X,Y> value) { chart.set(value); }
+    /* (non-Javadoc)
+     * @see com.pivotal.javafx.scene.chart.Series#chartProperty()
+     */
+    @Override
+    public final ReadOnlyObjectProperty<MultiAxisChart<X,Y>> chartProperty() { return chart.getReadOnlyProperty(); }
+
+    /** Reference to the Y Axis this chart belongs to */
+    private final ReadOnlyObjectWrapper<Axis<Y>> yAxis = new ReadOnlyObjectWrapper<Axis<Y>>(null, "yAxis") {
+      protected void invalidated() {
+        final Axis<Y> axis = get();
+        axis.autoRangingProperty().addListener(new ChangeListener<Boolean>() {
+          @Override
+          public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
+            if (newValue) {
+              // TODO keep lists in series changed listener?
+              axis.invalidateRange(getData().stream().map((Data<X,Y> d) -> d.getYValue()).collect(Collectors.toList()));
+            }
+          }
+        });
+      };
+    };
+    
+    /* (non-Javadoc)
+     * @see com.pivotal.javafx.scene.chart.Series#getYAxis()
+     */
+    @Override
+    public final Axis<Y> getYAxis() { return yAxis.get(); }
+    // TODO change axis event
+    /* (non-Javadoc)
+     * @see com.pivotal.javafx.scene.chart.Series#setYAxis(javafx.scene.chart.Axis)
+     */
+    @Override
+    public void setYAxis(Axis<Y> value) { yAxis.set(value); }
+    /* (non-Javadoc)
+     * @see com.pivotal.javafx.scene.chart.Series#yAxisProperty()
+     */
+    @Override
+    public final ReadOnlyObjectProperty<Axis<Y>> yAxisProperty() { return yAxis.getReadOnlyProperty(); }
+
+    /** Reference to the legend for this series */
+    private final ReadOnlyObjectWrapper<LegendItem> legendItem = new ReadOnlyObjectWrapper<>(null, "legendItem");
+    /* (non-Javadoc)
+     * @see com.pivotal.javafx.scene.chart.Series#getLegendItem()
+     */
+    @Override
+    public final LegendItem getLegendItem() { return legendItem.get(); }
+    
+    @Override
+    public  void setLegendItem(LegendItem value) { legendItem.set(value); }
+    /* (non-Javadoc)
+     * @see com.pivotal.javafx.scene.chart.Series#legendItemProperty()
+     */
+    @Override
+    public final ReadOnlyObjectProperty<LegendItem> legendItemProperty() { return legendItem.getReadOnlyProperty(); }
+
+    
+    /** The user displayable name for this series */
+    private final StringProperty name = new StringPropertyBase() {
+        @Override protected void invalidated() {
+            get(); // make non-lazy
+            if(getChart() != null) getChart().seriesNameChanged();
+        }
+
+        @Override
+        public Object getBean() {
+            return AbstractSeries.this;
+        }
+
+        @Override
+        public String getName() {
+            return "name";
+        }
+    };
+    /* (non-Javadoc)
+     * @see com.pivotal.javafx.scene.chart.Series#getName()
+     */
+    @Override
+    public final String getName() { return name.get(); }
+    /* (non-Javadoc)
+     * @see com.pivotal.javafx.scene.chart.Series#setName(java.lang.String)
+     */
+    @Override
+    public final void setName(String value) { name.set(value); }
+    /* (non-Javadoc)
+     * @see com.pivotal.javafx.scene.chart.Series#nameProperty()
+     */
+    @Override
+    public final StringProperty nameProperty() { return name; }
+
+    /**
+     * The node to display for this series. This is created by the chart if it uses nodes to represent the whole
+     * series. For example line chart uses this for the line but scatter chart does not use it. This node will be
+     * set as soon as the series is added to the chart. You can then get it to add mouse listeners etc.
+     */
+    private ObjectProperty<Node> node = new SimpleObjectProperty<Node>(this, "node");
+    /* (non-Javadoc)
+     * @see com.pivotal.javafx.scene.chart.Series#getNode()
+     */
+    @Override
+    public final Node getNode() { return node.get(); }
+    /* (non-Javadoc)
+     * @see com.pivotal.javafx.scene.chart.Series#setNode(javafx.scene.Node)
+     */
+    @Override
+    public final void setNode(Node value) { node.set(value); }
+    /* (non-Javadoc)
+     * @see com.pivotal.javafx.scene.chart.Series#nodeProperty()
+     */
+    @Override
+    public final ObjectProperty<Node> nodeProperty() { return node; }
+
+
+    // -------------- CONSTRUCTORS ----------------------------------------------
+
+    /**
+     * Construct a empty series
+     */
+    public AbstractSeries() {
+    }
+
+    /**
+     * 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 AbstractSeries(String name, ObservableList<Data<X,Y>> data) {
+        setName(name);
+    }
+
+    // -------------- PUBLIC METHODS ----------------------------------------------
+
+    /* (non-Javadoc)
+     * @see com.pivotal.javafx.scene.chart.Series#toString()
+     */ 
+    @Override public String toString() {
+        return "Series["+getName()+"]";
+    }
+
+    // -------------- PRIVATE/PROTECTED METHODS -----------------------------------
+
+    /*
+     * The following methods are for manipulating the pointers in the linked list
+     * when data is deleted. 
+     */
+//    @Override
+//    public void removeDataItemRef(Data<X,Y> item) {
+//        if (begin == item) {
+//            begin = item.next;
+//        } else {
+//            Data<X,Y> ptr = begin;
+//            while(ptr != null && ptr.next != item) {
+//                ptr = ptr.next;
+//            }
+//            if(ptr != null) ptr.next = item.next;
+//        }
+//    }
+
+    @Override
+    public int getItemIndex(Data<X,Y> item) {
+//        int itemIndex = 0;
+//        for (Data<X,Y> d = begin; d != null; d = d.next) {
+//            if (d == item) break;
+//            itemIndex++;
+//        }
+//        return itemIndex;
+      return getData().indexOf(item);
+    }
+
+    @Override
+    public int getDataSize() {
+//        int count = 0;
+//        for (Data<X,Y> d = begin; d != null; d = d.next) {
+//            count++;
+//        }
+//        return count;
+      return getData().size();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/e299ea84/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/BasicDataSet.java
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/BasicDataSet.java b/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/BasicDataSet.java
new file mode 100644
index 0000000..7ec0c5c
--- /dev/null
+++ b/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/BasicDataSet.java
@@ -0,0 +1,134 @@
+package com.pivotal.javafx.scene.chart;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import javafx.beans.InvalidationListener;
+
+public class BasicDataSet<X extends Number & Comparable<X>, Y extends Number & Comparable<Y>> implements DataSet<X, Y> {
+
+  protected final List<Data<X,Y>> list = new ArrayList<>();
+  
+  public void addData(final Data<X,Y> data) {
+    list.add(data);
+    
+    // TODO fire change;
+  }
+  
+  @Override
+  public List<Data<X, Y>> getData(final X from, final X to, final int limit) {
+    
+    List<Data<X, Y>> visible = list.stream().filter(new Predicate<Data<X,Y>>() {
+      @Override
+      public boolean test(Data<X, Y> item) {
+        final X x = item.getXValue();
+        return between(from, to, x);
+      }
+
+    }).collect(Collectors.toList());
+    
+    //System.out.println(getData().size() + " - " + visible.size());
+    
+    if (visible.size() > limit) {
+      visible = getVisibleDataSubsampled(visible, limit);
+    }
+    
+    return visible;
+  }
+  
+  private List<Data<X, Y>> getVisibleDataSubsampled(List<Data<X, Y>> data, int threshold) {
+    // Bucket size. Leave room for start and end data points
+    final int dataLength = data.size();
+    final double bucketSize = (double) (dataLength - 2) / (threshold - 2);
+    final ArrayList<Data<X,Y>> sampled = new ArrayList<>(threshold);
+
+    int a = 0; // Initially a is the first point in the triangle
+    int nextA = 0;
+
+    sampled.add(data.get(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++) {
+        final Data<X, Y> item = data.get(pointCStart);
+        // TODO use the axis position? Don't have to assume number type then
+        pointCX += item.getXValue().doubleValue();
+        pointCY +=item.getYValue().doubleValue();
+      }
+      pointCX /= pointCSize;
+      pointCY /= pointCSize;
+
+      // Point a
+      final double pointAX;
+      final double pointAY;
+      {
+        final Data<X, Y> item = data.get(a);
+        pointAX = item.getXValue().doubleValue();
+        pointAY = item.getYValue().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;
+      for (; pointBStart < pointBEnd; pointBStart++) {
+        // Calculate triangle area over three buckets
+        final Data<X, Y> item = data.get(pointBStart);
+        final double pointBX = item.getXValue().doubleValue();
+        final double pointBY = item.getYValue().doubleValue();
+        final double area = Math.abs((pointAX - pointCX) * (pointBY - pointAY) - (pointAX - pointBX)
+            * (pointCY - pointAY)) * 0.5;
+        if (area > maxArea) {
+          maxArea = area;
+          nextA = pointBStart; // Next a is this b
+        }
+      }
+
+     // Pick this point from the bucket
+      sampled.add(data.get(nextA)); 
+      a = nextA; // This a is the next a (chosen b)
+    }
+    
+    // Always add last
+    sampled.add(data.get(dataLength - 1));
+    
+    return sampled;
+  }
+
+  @Override
+  public void addListener(InvalidationListener listener) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void removeListener(InvalidationListener listener) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void addListener(DataChangedListener listener) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void removeListener(DataChangedListener listener) {
+    // TODO Auto-generated method stub
+
+  }
+
+  protected static <T extends Comparable<T>> boolean between(final T from, final T to, final T value) {
+    return from.compareTo(value) >= 0 && to.compareTo(value) <= 0;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/e299ea84/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/ByteBufferNumberSeries.java
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/ByteBufferNumberSeries.java b/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/ByteBufferNumberSeries.java
new file mode 100644
index 0000000..e2a3e52
--- /dev/null
+++ b/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/ByteBufferNumberSeries.java
@@ -0,0 +1,432 @@
+package com.pivotal.javafx.scene.chart;
+
+import java.nio.ByteBuffer;
+import java.text.MessageFormat;
+import java.text.NumberFormat;
+import java.util.AbstractCollection;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ObjectPropertyBase;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.scene.Node;
+import javafx.scene.chart.Axis;
+
+import com.pivotal.jvsd.fx.HoveredThresholdNode;
+
+/**
+ * A named series of data items
+ */
+public abstract class ByteBufferNumberSeries<X extends Number, Y extends Number> extends BasicSeries<X, Y> {
+
+  // -------------- PUBLIC PROPERTIES ----------------------------------------
+
+  // TODO remove
+  /** ObservableList of data items that make up this series */
+  private final ObjectProperty<ByteBuffer> bufferProperty = new SimpleObjectProperty<ByteBuffer>() {
+    protected void invalidated() {
+      buffer = get();
+    }
+  };
+
+  // here for speed over getBuffer();
+  protected ByteBuffer buffer;
+
+  public final ByteBuffer getBuffer() {
+    return bufferProperty.get();
+  }
+
+  public final void setBuffer(ByteBuffer value) {
+    bufferProperty.setValue(value);
+  }
+
+  public final ObjectProperty<ByteBuffer> bufferProperty() {
+    return bufferProperty;
+  }
+
+//  // TODO remove
+//  /** ObservableList of data items that make up this series */
+//  private final ObjectProperty<ObservableList<Data<X, Y>>> data = new ObjectPropertyBase<ObservableList<Data<X, Y>>>() {
+//    @Override
+//    protected void invalidated() {
+//      // TODO update buffer, minX, maxX, etc.
+//    }
+//
+//    @Override
+//    public Object getBean() {
+//      return ByteBufferNumberSeries.this;
+//    }
+//
+//    @Override
+//    public String getName() {
+//      return "data";
+//    }
+//  };
+//
+//  /*
+//   * (non-Javadoc)
+//   * 
+//   * @see com.pivotal.javafx.scene.chart.Series#getData()
+//   */
+//  // TODO remove
+//  @Override
+//  public final ObservableList<Data<X, Y>> getData() {
+//    return data.getValue();
+//    // System.out.println("getData:");
+//    // return ;
+//  }
+//
+//  /*
+//   * (non-Javadoc)
+//   * 
+//   * @see
+//   * com.pivotal.javafx.scene.chart.Series#setData(javafx.collections.ObservableList
+//   * )
+//   */
+//  // TODO remove
+//  @Override
+//  public final void setData(ObservableList<Data<X, Y>> value) {
+//    data.setValue(value);
+//  }
+//
+//  /*
+//   * (non-Javadoc)
+//   * 
+//   * @see com.pivotal.javafx.scene.chart.Series#dataProperty()
+//   */
+//  // TODO remove
+//  @Override
+//  public final ObjectProperty<ObservableList<Data<X, Y>>> dataProperty() {
+//    return data;
+//  }
+
+  // -------------- CONSTRUCTORS ----------------------------------------------
+
+  /**
+   * Construct a empty series
+   */
+  public ByteBufferNumberSeries() {
+    super();
+    buffer = null;
+  }
+
+  /**
+   * Constructs a Series and populates it with the given {@link ObservableList}
+   * data.
+   *
+   * @param data
+   *          ObservableList of MultiAxisChart.Data
+   */
+  public ByteBufferNumberSeries(ByteBuffer buffer) {
+    super();
+    setBuffer(buffer);
+    //setData(FXCollections.observableArrayList(new Data(0, 1000000), new Data(10000000000000l, -100000)));
+    setData(FXCollections.observableArrayList(new ThresholdDataCollection(0, getDataSize(), 1000)));
+    
+  }
+
+  /**
+   * 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 ByteBufferNumberSeries(String name, ByteBuffer buffer) {
+    this(buffer);
+    setName(name);
+  }
+
+  @Override
+  public Collection<Data<X, Y>> getVisibleData() {
+
+    final Axis<X> xAxis = getChart().getXAxis();
+    final double width = xAxis.getWidth();
+
+    final int end = getDataSize() - 1;
+
+    final double min = getX(0);
+    final double max = getX(end);
+
+    final double left = xAxis.getValueForDisplay(0).doubleValue();
+    final double right = xAxis.getValueForDisplay(width).doubleValue();
+
+    // find file position for first visible point
+    int first;
+    if (left <= min) {
+      first = 0;
+    } else if (left >= max) {
+      first = end;
+    } else {
+      // time series should be linear on x, so guess file position and scan
+      first = (int) (((left - min) / (max - min)) * end);
+      if (getX(first) < left) {
+        first = findFirst(first + 1, left) - 1;
+      } else {
+        first = findLast(first - 1, left);
+      }
+    }
+
+    // find file position for last visible point
+    int last;
+    if (right <= min) {
+      last = 0;
+    } else if (right >= max) {
+      last = end;
+    } else {
+      // time series should be linear on x, so guess file position and scan
+      last = (int) (((right - min) / (max - min)) * end);
+      if (getX(last) < right) {
+        last = findFirst(last + 1, right);
+      } else {
+        last = findLast(last - 1, right) + 1;
+      }
+    }
+
+    // fit threshold to series width
+    int threshold = (int) width;
+    if (left < min || right > max) {
+      // there is space on either/both ends so adjust the threshold
+      threshold = Math.max((int) (xAxis.getDisplayPosition(convertX(Math.min(right, max))) - xAxis.getDisplayPosition(convertX(Math.max(left, min)))), 3);
+    }
+    
+    // using 2 times the width makes it look a little smoother. Consider dynamic bucket size algorithm.
+    // you can still seem some points blink in and out on zoom and pan
+    // maybe used fixed points for bucket boundaries, first/last buckets will shrink by first/last data position
+    final Collection<Data<X, Y>> v = new ThresholdDataCollection(first, last - first + 1, threshold);
+
+    return v;
+  }
+
+
+
+  protected Data<X, Y> getData(final int index) {
+    Data<X, Y> data = createData(index);
+    data.setNode(createSymbol(data));
+    return data;
+  }
+  
+  
+  private Node createSymbol(Data<X, Y> data) {
+    return new HoveredThresholdNode(NumberFormat.getNumberInstance().format(data.getYValue()));
+  }
+
+
+  // TODO Make these Abstract
+  @Override
+  public abstract int getDataSize();
+
+  protected abstract Data<X,Y> createData(final int index);
+
+  protected abstract double getX(final int index);
+
+  protected abstract X convertX(double x);
+
+  protected abstract double getY(final int index);
+
+  protected abstract Y convertY(double y);
+
+  protected int findFirst(final int start, final double left) {
+    // TODO improve over scan? since likely linear chances are we hit on first try.
+    for (int i = start; i < getDataSize(); i++) {
+      if (getX(i) > left) {
+//        System.out.println("f: " + (i - start));
+        return i;
+      }
+    }
+
+    return -1;
+  }
+
+  protected int findLast(final int start, final double right) {
+    // TODO improve over scan? since likely linear chances are we hit on first try.
+    for (int i = start; i >= 0; i--) {
+      if (getX(i) < right) {
+//        System.out.println("l: " + (start - i));
+        return i;
+      }
+    }
+
+    return getDataSize() - 1;
+  }
+
+  protected class ThresholdDataCollection extends AbstractCollection<Data<X, Y>> {
+    final int offset;
+    final int length;
+    final int threshold;
+    final int size;
+
+    public ThresholdDataCollection(final int offset, final int length, final int threshold) {
+      this.offset = offset;
+      this.length = length;
+      this.threshold = threshold;
+      this.size = Math.min(length, (int) threshold);
+
+      System.out.println(MessageFormat.format("ThresholdDataCollection: offset={0}, length={1}, threshold={2}, end={3}", offset, length, threshold, offset + length));
+    }
+
+    @Override
+    public Iterator<Data<X, Y>> iterator() {
+      if (length > threshold) {
+        return createDownsampleVisibleIterator();
+      } else {
+        return createVisibleIterator();
+      }
+    }
+
+    @Override
+    public int size() {
+      return size;
+    }
+
+    protected Iterator<Data<X, Y>> createVisibleIterator() {
+      return new VisibleIterator();
+    }
+
+    protected Iterator<Data<X, Y>> createDownsampleVisibleIterator() {
+      return new DownsampleVisibleIterator();
+    }
+
+    protected abstract class AbstractDataIterator implements Iterator<Data<X, Y>> {
+      int index = offset;
+      Data<X, Y> next = null;
+      
+      protected abstract void advance();
+
+      protected Data<X, Y> consume() {
+        final Data<X, Y> data = next;
+        next = null;
+        return data;
+      }
+
+      @Override
+      public boolean hasNext() {
+        advance();
+        return (null != next);
+      }
+
+      @Override
+      public Data<X, Y> next() {
+        if (!hasNext()) {
+          throw new NoSuchElementException();
+        }
+
+        return consume();
+      }
+
+    }
+    
+    protected class VisibleIterator extends AbstractDataIterator {
+      
+       
+      @Override
+      protected void advance() {
+        if (null != next) {
+          return;
+        }
+
+        if (index >= (offset + length)) {
+          return;
+        }
+
+        next = getData(index);
+        index++;
+      }
+    }
+
+    protected class DownsampleVisibleIterator extends AbstractDataIterator {
+      // Bucket size. Leave room for start and end data points
+      final double bucketSize = (double) (length - 2) / (threshold - 2);
+
+      int bucket = 0;
+      int a = index; // Initially a is the first point in the triangle
+
+      public DownsampleVisibleIterator() {
+        super();
+
+        System.out.println(MessageFormat.format("DownsampleVisibleIterator: bucketSize={0}", bucketSize));
+      }
+
+      @Override
+      protected void advance() {
+        // TODO when zooming out bound to visible width not chart width
+        if (null != next) {
+          return;
+        }
+
+        if (index >= (offset + length)) {
+          return;
+        }
+
+//         final long start = System.nanoTime();
+//         try {
+
+        if (offset == index) {
+          // Always add the first point
+          next = getData(index);
+          index++;
+          return;
+        }
+
+        if (bucket < threshold - 2) {
+          // final long start2 = System.nanoTime();
+          // Calculate point average for next bucket (containing c)
+          double pointCX = 0;
+          double pointCY = 0;
+          int pointCStart = (int) Math.floor((bucket + 1) * bucketSize) + offset + 1;
+          int pointCEnd = (int) Math.floor((bucket + 2) * bucketSize) + offset + 1;
+          pointCEnd = Math.min(pointCEnd, (offset + length));
+          final int pointCSize = pointCEnd - pointCStart;
+          for (; pointCStart < pointCEnd; pointCStart++) {
+            pointCX += getX(pointCStart);
+            pointCY += getY(pointCStart);
+          }
+          pointCX /= pointCSize;
+          pointCY /= pointCSize;
+          // System.out.println("time2: " + (System.nanoTime() - start2));
+
+          // Point a
+          // TODO cache?
+          final double pointAX = getX(a);
+          final double pointAY = getY(a);
+
+          // Get the range for bucket b
+          int pointBStart = (int) Math.floor((bucket + 0) * bucketSize) + offset + 1;
+          final int pointBEnd = (int) Math.floor((bucket + 1) * bucketSize) + offset + 1;
+          double maxArea = -1;
+          for (; pointBStart < pointBEnd; pointBStart++) {
+            index++;
+            // Calculate triangle area over three buckets
+            final double area = Math.abs((pointAX - pointCX) * (getY(pointBStart) - pointAY) - (pointAX - getX(pointBStart))
+                * (pointCY - pointAY)) * 0.5;
+            if (area > maxArea) {
+              maxArea = area;
+              a = pointBStart; // Next a is this b
+            }
+          }
+
+          // Pick this point from the bucket
+          next = getData(a);
+          bucket++;
+          return;
+        }
+
+        // Always add last
+        assert ((offset + length - 1) == index);
+        next = getData(index);
+        index++;
+//         } finally {
+//         System.out.println(MessageFormat.format("DownsampleVisibleIterator: bucket={0}, time={1}ms",
+//         bucket, ((double) System.nanoTime() - start) / 1000000));
+//         }
+      }
+    }
+
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/e299ea84/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/DataChangedListener.java
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/DataChangedListener.java b/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/DataChangedListener.java
new file mode 100644
index 0000000..e7cc1a7
--- /dev/null
+++ b/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/DataChangedListener.java
@@ -0,0 +1,5 @@
+package com.pivotal.javafx.scene.chart;
+
+public interface DataChangedListener {
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/e299ea84/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/DataSet.java
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/DataSet.java b/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/DataSet.java
new file mode 100644
index 0000000..bd67aef
--- /dev/null
+++ b/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/DataSet.java
@@ -0,0 +1,11 @@
+package com.pivotal.javafx.scene.chart;
+
+import java.util.List;
+
+public interface DataSet<X, Y> extends javafx.beans.Observable {
+  public List<Data<X, Y>> getData(X from, X to, int limit);
+
+  public void addListener(DataChangedListener listener);
+
+  public void removeListener(DataChangedListener listener);
+}

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/e299ea84/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/DateAxis.java
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/DateAxis.java b/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/DateAxis.java
new file mode 100644
index 0000000..d8242b6
--- /dev/null
+++ b/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/DateAxis.java
@@ -0,0 +1,393 @@
+/*
+ * Portions of this source copied with love from https://bitbucket.org/sco0ter/extfx
+ * under the following license:
+ * 
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013, Christian Schudt
+ *
+ * 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 com.pivotal.javafx.scene.chart;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import javafx.animation.KeyFrame;
+import javafx.animation.KeyValue;
+import javafx.beans.property.ReadOnlyDoubleWrapper;
+import javafx.scene.chart.ValueAxis;
+import javafx.util.Duration;
+import javafx.util.StringConverter;
+
+import com.sun.javafx.charts.ChartLayoutAnimator;
+
+/**
+ * Implemenation of {@link ValueAxis} similar to {@link NumberAxis} but with
+ * formatting for date and time.
+ * 
+ * @author jbarrett
+ * 
+ */
+public final class DateAxis extends ValueAxis<Number> {
+
+  private ChartLayoutAnimator animator = new ChartLayoutAnimator(this);
+
+  private Object currentAnimationID;
+
+  private Interval actualInterval = Interval.DECADE;
+
+  /**
+   * Default constructor. By default the lower and upper bound are calculated by
+   * the data.
+   */
+  public DateAxis() {
+  }
+
+  /**
+   * Constructs a date axis with fix lower and upper bounds.
+   * 
+   * @param lowerBound
+   *          The lower bound.
+   * @param upperBound
+   *          The upper bound.
+   */
+  public DateAxis(Long lowerBound, Long upperBound) {
+    this();
+    setAutoRanging(false);
+    setLowerBound(lowerBound);
+    setUpperBound(upperBound);
+  }
+
+  /**
+   * Constructs a date axis with a label and fix lower and upper bounds.
+   * 
+   * @param axisLabel
+   *          The label for the axis.
+   * @param lowerBound
+   *          The lower bound.
+   * @param upperBound
+   *          The upper bound.
+   */
+  public DateAxis(String axisLabel, Long lowerBound, Long upperBound) {
+    this(lowerBound, upperBound);
+    setLabel(axisLabel);
+  }
+
+  @Override
+  protected Object autoRange(double minValue, double maxValue, double length, double labelSize) {
+    return new Object[] { (long) minValue, (long) maxValue, calculateNewScale(length, (long) minValue, (long) maxValue) };
+  }
+
+  @Override
+  protected void setRange(Object range, boolean animating) {
+    Object[] r = (Object[]) range;
+    long oldLowerBound = (long) getLowerBound();
+    long lower = (long) r[0];
+    long upper = (long) r[1];
+    double scale = (double) r[2];
+    setLowerBound(lower);
+    setUpperBound(upper);
+
+    if (animating) {
+      animator.stop(currentAnimationID);
+      currentAnimationID = animator.animate(
+          new KeyFrame(Duration.ZERO,
+              new KeyValue(currentLowerBound, oldLowerBound),
+              new KeyValue(scalePropertyImplProtected(), getScale())),
+          new KeyFrame(Duration.millis(700),
+              new KeyValue(currentLowerBound, lower), 
+              new KeyValue(scalePropertyImplProtected(), scale)));
+
+    } else {
+      currentLowerBound.set(getLowerBound());
+      setScale(scale);
+    }
+  }
+
+  @Override
+  protected Object getRange() {
+    return new Object[] { (long) getLowerBound(), (long) getUpperBound() };
+  }
+
+  @Override
+  protected List<Number> calculateTickValues(double v, Object range) {
+    Object[] r = (Object[]) range;
+    long lower = (long) r[0];
+    long upper = (long) r[1];
+
+    List<Date> dateList = new ArrayList<Date>();
+    Calendar calendar = Calendar.getInstance();
+
+    // The preferred gap which should be between two tick marks.
+    double averageTickGap = 100;
+    double averageTicks = v / averageTickGap;
+
+    List<Date> previousDateList = new ArrayList<Date>();
+
+    Interval previousInterval = Interval.values()[0];
+
+    // Starting with the greatest interval, add one of each calendar unit.
+    for (Interval interval : Interval.values()) {
+      // Reset the calendar.
+      calendar.setTime(new Date(lower));
+      // Clear the list.
+      dateList.clear();
+      previousDateList.clear();
+      actualInterval = interval;
+
+      // Loop as long we exceeded the upper bound.
+      while (calendar.getTime().getTime() <= upper) {
+        dateList.add(calendar.getTime());
+        calendar.add(interval.interval, interval.amount);
+      }
+      // Then check the size of the list. If it is greater than the amount of
+      // ticks, take that list.
+      if (dateList.size() > averageTicks) {
+        calendar.setTime(new Date(lower));
+        // Recheck if the previous interval is better suited.
+        while (calendar.getTime().getTime() <= upper) {
+          previousDateList.add(calendar.getTime());
+          calendar.add(previousInterval.interval, previousInterval.amount);
+        }
+        break;
+      }
+
+      previousInterval = interval;
+    }
+    if (previousDateList.size() - averageTicks > averageTicks - dateList.size()) {
+      dateList = previousDateList;
+      actualInterval = previousInterval;
+    }
+
+    // At last add the upper bound.
+    dateList.add(new Date(upper));
+
+    List<Number> evenDateList = makeDatesEven(dateList, calendar);
+    // If there are at least three dates, check if the gap between the lower
+    // date and the second date is at least half the gap of the second and third
+    // date.
+    // Do the same for the upper bound.
+    // If gaps between dates are to small, remove one of them.
+    // This can occur, e.g. if the lower bound is 25.12.2013 and years are
+    // shown. Then the next year shown would be 2014 (01.01.2014) which would be
+    // too narrow to 25.12.2013.
+    if (evenDateList.size() > 2) {
+
+      long secondDate = evenDateList.get(1).longValue();
+      long thirdDate = evenDateList.get(2).longValue();
+      long lastDate = evenDateList.get(dateList.size() - 2).longValue();
+      long previousLastDate = evenDateList.get(dateList.size() - 3).longValue();
+
+      // If the second date is too near by the lower bound, remove it.
+      if (secondDate - lower < (thirdDate - secondDate) / 2) {
+        evenDateList.remove(secondDate);
+      }
+
+      // If difference from the upper bound to the last date is less than the
+      // half of the difference of the previous two dates,
+      // we better remove the last date, as it comes to close to the upper
+      // bound.
+      if (upper - lastDate < (lastDate - previousLastDate) / 2) {
+        evenDateList.remove(lastDate);
+      }
+    }
+
+    return evenDateList;
+  }
+
+  @Override
+  protected String getTickMarkLabel(final Number ts) {
+    final Date date = new Date(ts.longValue());
+
+    StringConverter<Number> converter = getTickLabelFormatter();
+    if (converter != null) {
+      return converter.toString(date.getTime());
+    }
+
+    DateFormat dateFormat;
+    Calendar calendar = Calendar.getInstance();
+    calendar.setTime(date);
+
+    if (actualInterval.interval == Calendar.YEAR && calendar.get(Calendar.MONTH) == 0 && calendar.get(Calendar.DATE) == 1) {
+      dateFormat = new SimpleDateFormat("yyyy");
+    } else if (actualInterval.interval == Calendar.MONTH && calendar.get(Calendar.DATE) == 1) {
+      dateFormat = new SimpleDateFormat("MMM yy");
+    } else {
+      switch (actualInterval.interval) {
+      case Calendar.DATE:
+      case Calendar.WEEK_OF_YEAR:
+      default:
+        dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM);
+        break;
+      case Calendar.HOUR:
+      case Calendar.MINUTE:
+        dateFormat = DateFormat.getTimeInstance(DateFormat.SHORT);
+        break;
+      case Calendar.SECOND:
+        dateFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM);
+        break;
+      case Calendar.MILLISECOND:
+        dateFormat = DateFormat.getTimeInstance(DateFormat.FULL);
+        break;
+      }
+    }
+    return dateFormat.format(date);
+  }
+
+  /**
+   * Makes dates even, in the sense of that years always begin in January,
+   * months always begin on the 1st and days always at midnight.
+   * 
+   * @param dates
+   *          The list of dates.
+   * @return The new list of dates.
+   */
+  private List<Number> makeDatesEven(List<Date> dates, Calendar calendar) {
+    // If the dates contain more dates than just the lower and upper bounds,
+    // make the dates in between even.
+    // TODO if (dates.size() > 2) {
+    List<Number> evenDates = new ArrayList<Number>();
+
+    // For each interval, modify the date slightly by a few millis, to make sure
+    // they are different days.
+    // This is because Axis stores each value and won't update the tick labels,
+    // if the value is already known.
+    // This happens if you display days and then add a date many years in the
+    // future the tick label will still be displayed as day.
+    for (int i = 0; i < dates.size(); i++) {
+      calendar.setTime(dates.get(i));
+      switch (actualInterval.interval) {
+      case Calendar.YEAR:
+        // If its not the first or last date (lower and upper bound), make the
+        // year begin with first month and let the months begin with first day.
+        if (i != 0 && i != dates.size() - 1) {
+          calendar.set(Calendar.MONTH, 0);
+          calendar.set(Calendar.DATE, 1);
+        }
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 6);
+        break;
+      case Calendar.MONTH:
+        // If its not the first or last date (lower and upper bound), make the
+        // months begin with first day.
+        if (i != 0 && i != dates.size() - 1) {
+          calendar.set(Calendar.DATE, 1);
+        }
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 5);
+        break;
+      case Calendar.WEEK_OF_YEAR:
+        // Make weeks begin with first day of week?
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 4);
+        break;
+      case Calendar.DATE:
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 3);
+        break;
+      case Calendar.HOUR:
+        if (i != 0 && i != dates.size() - 1) {
+          calendar.set(Calendar.MINUTE, 0);
+          calendar.set(Calendar.SECOND, 0);
+        }
+        calendar.set(Calendar.MILLISECOND, 2);
+        break;
+      case Calendar.MINUTE:
+        if (i != 0 && i != dates.size() - 1) {
+          calendar.set(Calendar.SECOND, 0);
+        }
+        calendar.set(Calendar.MILLISECOND, 1);
+        break;
+      case Calendar.SECOND:
+        calendar.set(Calendar.MILLISECOND, 0);
+        break;
+
+      }
+      evenDates.add(calendar.getTime().getTime());
+    }
+
+    return evenDates;
+    // } else {
+    // return dates;
+    // }
+  }
+
+  /**
+   * The intervals, which are used for the tick labels. Beginning with the
+   * largest interval, the axis tries to calculate the tick values for this
+   * interval. If a smaller interval is better suited for, that one is taken.
+   */
+  private enum Interval {
+    DECADE(Calendar.YEAR, 10), YEAR(Calendar.YEAR, 1), MONTH_6(Calendar.MONTH, 6), MONTH_3(Calendar.MONTH, 3), MONTH_1(Calendar.MONTH, 1), WEEK(
+        Calendar.WEEK_OF_YEAR, 1), DAY(Calendar.DATE, 1), HOUR_12(Calendar.HOUR, 12), HOUR_6(Calendar.HOUR, 6), HOUR_3(Calendar.HOUR, 3), HOUR_1(Calendar.HOUR,
+        1), MINUTE_15(Calendar.MINUTE, 15), MINUTE_5(Calendar.MINUTE, 5), MINUTE_1(Calendar.MINUTE, 1), SECOND_15(Calendar.SECOND, 15), SECOND_5(
+        Calendar.SECOND, 5), SECOND_1(Calendar.SECOND, 1), MILLISECOND(Calendar.MILLISECOND, 1);
+
+    private final int amount;
+
+    private final int interval;
+
+    private Interval(int interval, int amount) {
+      this.interval = interval;
+      this.amount = amount;
+    }
+  }
+
+  @Override
+  protected List<Number> calculateMinorTickMarks() {
+    return Collections.<Number>emptyList();
+  }
+
+  /* Access to package protected method */
+  private static final Method scalePropertyImplMethod;
+
+  static {
+    try {
+      scalePropertyImplMethod = ValueAxis.class.getDeclaredMethod("scalePropertyImpl");
+      scalePropertyImplMethod.setAccessible(true);
+    } catch (SecurityException | NoSuchMethodException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  protected ReadOnlyDoubleWrapper scalePropertyImplProtected() {
+    try {
+      return (ReadOnlyDoubleWrapper) scalePropertyImplMethod.invoke(this);
+    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/e299ea84/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/Series.java
----------------------------------------------------------------------
diff --git a/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/Series.java b/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/Series.java
new file mode 100644
index 0000000..73381d7
--- /dev/null
+++ b/jvsdfx-mm/src/main/java/com/pivotal/javafx/scene/chart/Series.java
@@ -0,0 +1,81 @@
+package com.pivotal.javafx.scene.chart;
+
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ReadOnlyObjectProperty;
+import javafx.beans.property.StringProperty;
+import javafx.collections.ObservableList;
+import javafx.scene.Node;
+import javafx.scene.chart.Axis;
+
+import com.pivotal.com.sun.javafx.charts.LegendItem;
+
+public interface Series<X, Y> {
+
+  public abstract void setChart(MultiAxisChart<X, Y> value);
+
+  public abstract MultiAxisChart<X, Y> getChart();
+
+  public abstract ReadOnlyObjectProperty<MultiAxisChart<X, Y>> chartProperty();
+
+  public abstract Axis<Y> getYAxis();
+  
+  // TODO change axis event
+  public abstract void setYAxis(Axis<Y> value);
+
+  public abstract ReadOnlyObjectProperty<Axis<Y>> yAxisProperty();
+
+  public abstract void setLegendItem(LegendItem value);
+
+  public abstract LegendItem getLegendItem();
+
+  public abstract ReadOnlyObjectProperty<LegendItem> legendItemProperty();
+
+  public abstract String getName();
+
+  public abstract void setName(String value);
+
+  public abstract StringProperty nameProperty();
+
+  public abstract Node getNode();
+
+  public abstract void setNode(Node value);
+
+  public abstract ObjectProperty<Node> nodeProperty();
+
+  public abstract ObservableList<Data<X, Y>> getData();
+
+  public abstract void setData(ObservableList<Data<X, Y>> value);
+
+  public abstract ObjectProperty<ObservableList<Data<X, Y>>> dataProperty();
+
+  /**
+   * Returns a string representation of this {@code Series} object.
+   * 
+   * @return a string representation of this {@code Series} object.
+   */
+  public abstract String toString();
+
+//  public abstract void removeDataItemRef(Data<X, Y> item);
+
+  public abstract int getItemIndex(Data<X, Y> item);
+
+  public abstract int getDataSize();
+
+  public abstract String getDefaultColorStyleClass();
+
+  public abstract void setDefaultColorStyleClass(String defaultColorStyleClass);
+
+//  public abstract Data<X, Y> getBegin();
+//
+//  public abstract void setBegin(Data<X, Y> begin);
+
+  public abstract Series<X, Y> getNext();
+
+  public abstract void setNext(Series<X, Y> next);
+
+  public abstract Iterable<Data<X, Y>> getVisibleData();
+
+//  public abstract Iterable<Data<X, Y>> getData(int from, int to, int limit);
+
+
+}
\ No newline at end of file



Mime
View raw message