Repository: knowm/XChart
Pull request #393 (2020-01-04 01:24:47)

Fix pr 318,  cursor support
@timmolter 
PR #318  has been fixed and is working, and the implementation method has been slightly changed.
Add style
```java
// Customize Chart
    chart
        .getStyler()
        .setCursorEnabled(true)
        .setCursorColor(Color.BLACK)
        .setCursorSize(1)
        .setCursorFont(new Font(Font.SANS_SERIF, Font.PLAIN, 16))
        .setCursorFontColor(Color.WHITE)
        .setCursorBackgroundColor(Color.GRAY);
```

The results are as follows:
![006](https://user-images.githubusercontent.com/57353473/71757763-6377e380-2ed3-11ea-82c2-5e5a6065f45f.gif)



Patch:
diff --git a/xchart-demo/src/main/java/org/knowm/xchart/demo/XChartDemo.java b/xchart-demo/src/main/java/org/knowm/xchart/demo/XChartDemo.java
index 724e8ad5..277f045b 100644
--- a/xchart-demo/src/main/java/org/knowm/xchart/demo/XChartDemo.java
+++ b/xchart-demo/src/main/java/org/knowm/xchart/demo/XChartDemo.java
@@ -52,6 +52,7 @@ import org.knowm.xchart.demo.charts.line.LineChart05;
 import org.knowm.xchart.demo.charts.line.LineChart06;
 import org.knowm.xchart.demo.charts.line.LineChart07;
 import org.knowm.xchart.demo.charts.line.LineChart08;
+import org.knowm.xchart.demo.charts.line.LineChart09;
 import org.knowm.xchart.demo.charts.ohlc.OHLCChart01;
 import org.knowm.xchart.demo.charts.ohlc.OHLCChart02;
 import org.knowm.xchart.demo.charts.ohlc.OHLCChart03;
@@ -372,6 +373,11 @@ public class XChartDemo extends JPanel implements TreeSelectionListener {
             new ChartInfo("LineChart08 - Step Rendering", new LineChart08().getChart()));
     category.add(defaultMutableTreeNode);
 
+    defaultMutableTreeNode =
+        new DefaultMutableTreeNode(
+            new ChartInfo("LineChart09 - Cursor", new LineChart09().getChart()));
+    category.add(defaultMutableTreeNode);
+
     // Scatter
     category = new DefaultMutableTreeNode("Scatter Charts");
     top.add(category);
diff --git a/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart09.java b/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart09.java
new file mode 100644
index 00000000..7de240cd
--- /dev/null
+++ b/xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart09.java
@@ -0,0 +1,46 @@
+package org.knowm.xchart.demo.charts.line;
+
+import org.knowm.xchart.SwingWrapper;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries.XYSeriesRenderStyle;
+import org.knowm.xchart.demo.charts.ExampleChart;
+import org.knowm.xchart.style.Styler.LegendPosition;
+
+public class LineChart09 implements ExampleChart<XYChart> {
+
+  public static void main(String[] args) {
+
+    ExampleChart<XYChart> exampleChart = new LineChart09();
+    XYChart chart = exampleChart.getChart();
+    new SwingWrapper<XYChart>(chart).displayChart();
+  }
+
+  @Override
+  public XYChart getChart() {
+
+    // Create Chart
+    XYChart chart =
+        new XYChartBuilder()
+            .width(800)
+            .height(600)
+            .title(getClass().getSimpleName())
+            .xAxisTitle("X")
+            .yAxisTitle("Y")
+            .build();
+
+    // Customize Chart
+    chart.getStyler().setLegendPosition(LegendPosition.OutsideE);
+    chart.getStyler().setAxisTitlesVisible(false);
+    chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Line);
+
+    chart.getStyler().setCursorEnabled(true);
+
+    // Series
+    chart.addSeries("a", new double[] {0, 3, 5, 7, 9}, new double[] {-3, 5, 9, 6, 5});
+    chart.addSeries("b", new double[] {0, 2.7, 4.8, 6, 9}, new double[] {-1, 6, 4, 0, 4});
+    chart.addSeries("c", new double[] {0, 1.5, 5, 8, 9}, new double[] {-2, -1, 1, 0, 1});
+
+    return chart;
+  }
+}
diff --git a/xchart/src/main/java/org/knowm/xchart/XChartPanel.java b/xchart/src/main/java/org/knowm/xchart/XChartPanel.java
index d5a4ce3c..b89625ae 100644
--- a/xchart/src/main/java/org/knowm/xchart/XChartPanel.java
+++ b/xchart/src/main/java/org/knowm/xchart/XChartPanel.java
@@ -10,6 +10,7 @@ import javax.swing.filechooser.FileFilter;
 import org.knowm.xchart.BitmapEncoder.BitmapFormat;
 import org.knowm.xchart.VectorGraphicsEncoder.VectorGraphicsFormat;
 import org.knowm.xchart.internal.chartpart.Chart;
+import org.knowm.xchart.internal.chartpart.Cursor;
 import org.knowm.xchart.internal.chartpart.ToolTips;
 import org.knowm.xchart.style.AxesChartStyler;
 import org.knowm.xchart.style.Styler;
@@ -52,6 +53,9 @@ public class XChartPanel<T extends Chart> extends JPanel {
       }
     }
 
+    // Mouse motion listener for Cursor
+    this.addMouseMotionListener(chart.getCursor());
+
     // Control+S key listener for saving chart
     KeyStroke ctrlS =
         KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
diff --git a/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Chart.java b/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Chart.java
index 6769e49e..b0363b98 100644
--- a/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Chart.java
+++ b/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Chart.java
@@ -25,7 +25,8 @@ public abstract class Chart<ST extends Styler, S extends Series> {
   protected final ChartTitle<ST, S> chartTitle;
   protected final Map<String, S> seriesMap = new LinkedHashMap<>();
   protected final List<String> infoContent = new ArrayList<>();
-  final ToolTips toolTips; // ToolTips is here because XChartPanel needs it for mouse listening
+  final ToolTips toolTips; // ToolTip is here because AxisPair and Plot need access to it
+  final Cursor cursor;
   /** Chart Parts */
   protected AxisPair axisPair;
 
@@ -57,6 +58,7 @@ public abstract class Chart<ST extends Styler, S extends Series> {
     this.styler = styler;
 
     this.toolTips = new ToolTips(styler);
+    this.cursor = new Cursor(styler);
 
     this.chartTitle = new ChartTitle<ST, S>(this);
   }
@@ -309,4 +311,9 @@ public abstract class Chart<ST extends Styler, S extends Series> {
 
     return toolTips;
   }
+
+  public Cursor getCursor() {
+
+    return cursor;
+  }
 }
diff --git a/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Cursor.java b/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Cursor.java
new file mode 100644
index 00000000..466c7a52
--- /dev/null
+++ b/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Cursor.java
@@ -0,0 +1,265 @@
+package org.knowm.xchart.internal.chartpart;
+
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.knowm.xchart.internal.series.MarkerSeries;
+import org.knowm.xchart.internal.series.Series;
+import org.knowm.xchart.style.Styler;
+
+/**
+ * Cursor movement to display matching point data information.
+ *
+ * @author Mr14huashao
+ */
+public class Cursor implements MouseMotionListener {
+
+  private static final int LINE_SPACING = 5;
+
+  private static final int MOUSE_SPACING = 15;
+
+  private final List<DataPoint> dataPointList = new ArrayList<>();
+
+  private final List<DataPoint> matchingDataPointList = new ArrayList<>();
+
+  private final Styler styler;
+
+  private Rectangle2D bounds;
+
+  private Map<String, Series> seriesMap;
+
+  private double mouseX;
+
+  private double mouseY;
+
+  private double startX;
+
+  private double startY;
+
+  private double textHeigh;
+
+  Cursor(Styler styler) {
+
+    this.styler = styler;
+  }
+
+  @Override
+  public void mouseDragged(MouseEvent e) {
+
+    // ignore
+  }
+
+  @Override
+  public void mouseMoved(MouseEvent e) {
+
+    // don't draw anything
+    if (!styler.isCursorEnabled()) {
+      return;
+    }
+
+    mouseX = e.getX();
+    mouseY = e.getY();
+    if (isMouseOut()) {
+
+      if (matchingDataPointList.size() > 0) {
+        matchingDataPointList.clear();
+        e.getComponent().repaint();
+      }
+      return;
+    }
+    List<DataPoint> dataPoints = new ArrayList<>();
+    for (DataPoint dataPoint : dataPointList) {
+      if (dataPoint.shape.contains(mouseX, dataPoint.shape.getBounds().getCenterY())) {
+        dataPoints.add(dataPoint);
+      }
+    }
+
+    if (dataPoints.size() > 0) {
+      matchingDataPointList.clear();
+      matchingDataPointList.addAll(dataPoints);
+    }
+    e.getComponent().repaint();
+  }
+
+  public void paint(Graphics2D g) {
+
+    if (!styler.isCursorEnabled()) {
+      return;
+    }
+
+    if (matchingDataPointList.size() > 0) {
+      DataPoint firstDataPoint = matchingDataPointList.get(0);
+
+      TextLayout xValueTextLayout =
+          new TextLayout(
+              firstDataPoint.xValue,
+              styler.getCursorFont(),
+              new FontRenderContext(null, true, false));
+      textHeigh = xValueTextLayout.getBounds().getHeight();
+
+      paintVerticalLine(g, firstDataPoint);
+
+      paintBackGround(g, xValueTextLayout);
+
+      painDataPointInfo(g, xValueTextLayout);
+    }
+  }
+
+  private void paintVerticalLine(Graphics2D g, DataPoint dataPoint) {
+
+    BasicStroke stroke =
+        new BasicStroke(styler.getCursorSize(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
+    g.setStroke(stroke);
+    g.setColor(styler.getCursorColor());
+    Line2D.Double line = new Line2D.Double();
+    line.setLine(dataPoint.x, bounds.getY(), dataPoint.x, bounds.getY() + bounds.getHeight());
+    g.draw(line);
+  }
+
+  private void paintBackGround(Graphics2D g, TextLayout xValueTextLayout) {
+
+    double maxLinewidth = xValueTextLayout.getBounds().getWidth();
+    TextLayout dataPointTextLayout = null;
+    Rectangle2D dataPointRectangle = null;
+    for (DataPoint dataPoint : matchingDataPointList) {
+      dataPointTextLayout =
+          new TextLayout(
+              dataPoint.seriesName + ": " + dataPoint.yValue,
+              styler.getCursorFont(),
+              new FontRenderContext(null, true, false));
+      dataPointRectangle = dataPointTextLayout.getBounds();
+      if (maxLinewidth < dataPointRectangle.getWidth()) {
+        maxLinewidth = dataPointRectangle.getWidth();
+      }
+    }
+
+    double backgroundWidth = styler.getCursorFont().getSize() + maxLinewidth + 3 * LINE_SPACING;
+    double backgroundHeight =
+        textHeigh * (1 + matchingDataPointList.size())
+            + (2 + matchingDataPointList.size()) * LINE_SPACING;
+
+    startX = mouseX;
+    startY = mouseY;
+    if (mouseX + MOUSE_SPACING + backgroundWidth > bounds.getX() + bounds.getWidth()) {
+      startX = mouseX - backgroundWidth - MOUSE_SPACING;
+    }
+
+    if (mouseY + MOUSE_SPACING + backgroundHeight > bounds.getY() + bounds.getHeight()) {
+      startY = mouseY - backgroundHeight - MOUSE_SPACING;
+    }
+
+    g.setColor(styler.getCursorBackgroundColor());
+    g.fillRect(
+        (int) startX + MOUSE_SPACING,
+        (int) startY + MOUSE_SPACING,
+        (int) (backgroundWidth),
+        (int) (backgroundHeight));
+  }
+
+  private void painDataPointInfo(Graphics2D g, TextLayout xValueTextLayout) {
+    AffineTransform orig = g.getTransform();
+    AffineTransform at = new AffineTransform();
+    at.translate(
+        startX + MOUSE_SPACING + LINE_SPACING, startY + textHeigh + MOUSE_SPACING + LINE_SPACING);
+    g.transform(at);
+    g.setColor(styler.getCursorFontColor());
+    g.fill(xValueTextLayout.getOutline(null));
+
+    MarkerSeries series = null;
+    TextLayout dataPointTextLayout = null;
+    Shape circle = null;
+    for (DataPoint dataPoint : matchingDataPointList) {
+      at = new AffineTransform();
+      at.translate(0, textHeigh + LINE_SPACING);
+      g.transform(at);
+      series = (MarkerSeries) seriesMap.get(dataPoint.seriesName);
+      g.setColor(series.getMarkerColor());
+      circle = new Ellipse2D.Double(0, -textHeigh, textHeigh, textHeigh);
+      g.fill(circle);
+
+      at = new AffineTransform();
+      at.translate(textHeigh + LINE_SPACING, 0);
+      g.transform(at);
+      g.setColor(styler.getCursorFontColor());
+      dataPointTextLayout =
+          new TextLayout(
+              dataPoint.seriesName + ": " + dataPoint.yValue,
+              styler.getCursorFont(),
+              new FontRenderContext(null, true, false));
+      g.fill(dataPointTextLayout.getOutline(null));
+
+      at = new AffineTransform();
+      at.translate(-textHeigh - LINE_SPACING, 0);
+      g.transform(at);
+    }
+    g.setTransform(orig);
+  }
+
+  void prepare(Rectangle2D bounds, Map<String, Series> seriesMap) {
+
+    if (!styler.isCursorEnabled()) {
+      return;
+    }
+    // clear lists
+    dataPointList.clear();
+
+    this.bounds = bounds;
+    this.seriesMap = seriesMap;
+  }
+
+  void addData(double xOffset, double yOffset, String xValue, String yValue, String seriesName) {
+
+    DataPoint dataPoint = new DataPoint(xOffset, yOffset, xValue, yValue, seriesName);
+    dataPointList.add(dataPoint);
+  }
+
+  private boolean isMouseOut() {
+
+    boolean isMouseOut = false;
+    if (!bounds.contains(mouseX, mouseY)) {
+      isMouseOut = true;
+    }
+    return isMouseOut;
+  }
+
+  private class DataPoint {
+
+    // edge detection
+    private static final int MARGIN = 5;
+
+    // Used to determine the point that the mouse has passed vertically
+    final Shape shape;
+    final double x;
+    final double y;
+    final String xValue;
+    final String yValue;
+    final String seriesName;
+
+    public DataPoint(double x, double y, String xValue, String yValue, String seriesName) {
+
+      double halfSize = MARGIN * 1.5;
+      double markerSize = MARGIN * 3;
+
+      this.x = x;
+      this.y = y;
+      this.shape =
+          new Ellipse2D.Double(this.x - halfSize, this.y - halfSize, markerSize, markerSize);
+
+      this.xValue = xValue;
+      this.yValue = yValue;
+      this.seriesName = seriesName;
+    }
+  }
+}
diff --git a/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_.java b/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_.java
index b80955f9..49dc6565 100644
--- a/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_.java
+++ b/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_.java
@@ -3,6 +3,8 @@ package org.knowm.xchart.internal.chartpart;
 import java.awt.*;
 import java.awt.geom.Path2D;
 import java.awt.geom.Rectangle2D;
+import java.util.Map;
+
 import org.knowm.xchart.internal.series.Series;
 import org.knowm.xchart.style.Styler;
 
@@ -45,10 +47,14 @@ public abstract class PlotContent_<ST extends Styler, S extends Series> implemen
 
     chart.toolTips.prepare(g);
 
+    chart.cursor.prepare(bounds, (Map<String, Series>) chart.getSeriesMap());
+
     doPaint(g);
 
     chart.toolTips.paint(g);
 
+    chart.cursor.paint(g);
+
     for (ChartPart part : chart.getPlotParts()) {
       part.paint(g);
     }
diff --git a/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_XY.java b/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_XY.java
index e1e05186..2e997b43 100644
--- a/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_XY.java
+++ b/xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_XY.java
@@ -302,6 +302,15 @@ public class PlotContent_XY<ST extends AxesChartStyler, S extends XYSeries>
                 chart.getYAxisFormat().format(yOrig));
           }
         }
+
+        if (xyStyler.isCursorEnabled()) {
+          chart.cursor.addData(
+              xOffset,
+              yOffset,
+              chart.getXAxisFormat().format(x),
+              chart.getYAxisFormat().format(yOrig),
+              series.getName());
+        }
       }
 
       if (smoothPath != null) {
diff --git a/xchart/src/main/java/org/knowm/xchart/style/AbstractBaseTheme.java b/xchart/src/main/java/org/knowm/xchart/style/AbstractBaseTheme.java
index e817cbff..7e2db8b1 100644
--- a/xchart/src/main/java/org/knowm/xchart/style/AbstractBaseTheme.java
+++ b/xchart/src/main/java/org/knowm/xchart/style/AbstractBaseTheme.java
@@ -393,6 +393,44 @@ public abstract class AbstractBaseTheme implements Theme {
     return ChartColor.getAWTColor(ChartColor.LIGHT_GREY);
   }
 
+  // Cursor ///////////////////////////////
+
+  @Override
+  public boolean isCursorEnabled() {
+
+    return false;
+  }
+
+  @Override
+  public Color getCursorColor() {
+
+    return Color.BLACK;
+  }
+
+  @Override
+  public float getCursorSize() {
+
+    return 1;
+  }
+
+  @Override
+  public Font getCursorFont() {
+
+    return new Font(Font.SANS_SERIF, Font.PLAIN, 16);
+  }
+
+  @Override
+  public Color getCursorFontColor() {
+
+    return Color.WHITE;
+  }
+
+  @Override
+  public Color getCursorBackgroundColor() {
+
+    return Color.GRAY;
+  }
+
   // Category Charts ///////////////////////////////
 
   @Override
diff --git a/xchart/src/main/java/org/knowm/xchart/style/Styler.java b/xchart/src/main/java/org/knowm/xchart/style/Styler.java
index 44bfc29a..f9415bfb 100644
--- a/xchart/src/main/java/org/knowm/xchart/style/Styler.java
+++ b/xchart/src/main/java/org/knowm/xchart/style/Styler.java
@@ -59,6 +59,13 @@ public abstract class Styler {
   private Color toolTipBorderColor;
   private Font toolTipFont;
   private Color toolTipHighlightColor;
+  // Cursor ////////////////////////////////
+  private boolean isCursorEnabled;
+  private Color cursorColor;
+  private float cursorSize;
+  private Font cursorFont;
+  private Color cursorFontColor;
+  private Color cursorBackgroundColor;
   // Annotations ///////////////////////////////
   private Font annotationsFont;
   private Color annotationsFontColor;
@@ -124,6 +131,14 @@ public abstract class Styler {
     toolTipFont = theme.getToolTipFont();
     toolTipHighlightColor = theme.getToolTipHighlightColor();
 
+    // Cursor ////////////////////////////////
+    this.isCursorEnabled = theme.isCursorEnabled();
+    this.cursorColor = theme.getCursorColor();
+    this.cursorSize = theme.getCursorSize();
+    this.cursorFont = theme.getCursorFont();
+    this.cursorFontColor = theme.getCursorFontColor();
+    this.cursorBackgroundColor = theme.getCursorBackgroundColor();
+
     // Annotations ///////////////////////////////
     annotationsFont = theme.getAnnotationFont();
     annotationsFontColor = theme.getAnnotationsFontColor();
@@ -682,6 +697,72 @@ public abstract class Styler {
     return this;
   }
 
+  // Cursor ///////////////////////////////
+
+  public boolean isCursorEnabled() {
+    return isCursorEnabled;
+  }
+
+  public Styler setCursorEnabled(boolean isCursorEnabled) {
+
+    this.isCursorEnabled = isCursorEnabled;
+    return this;
+  }
+
+  public Color getCursorColor() {
+    return cursorColor;
+  }
+
+  public Styler setCursorColor(Color cursorColor) {
+
+    this.cursorColor = cursorColor;
+    return this;
+  }
+
+  public float getCursorSize() {
+
+    return cursorSize;
+  }
+
+  public Styler setCursorSize(float cursorSize) {
+
+    this.cursorSize = cursorSize;
+    return this;
+  }
+
+  public Font getCursorFont() {
+
+    return cursorFont;
+  }
+
+  public Styler setCursorFont(Font cursorFont) {
+
+    this.cursorFont = cursorFont;
+    return this;
+  }
+
+  public Color getCursorFontColor() {
+
+    return cursorFontColor;
+  }
+
+  public Styler setCursorFontColor(Color cursorFontColor) {
+
+    this.cursorFontColor = cursorFontColor;
+    return this;
+  }
+
+  public Color getCursorBackgroundColor() {
+
+    return cursorBackgroundColor;
+  }
+
+  public Styler setCursorBackgroundColor(Color cursorBackgroundColor) {
+
+    this.cursorBackgroundColor = cursorBackgroundColor;
+    return this;
+  }
+
   public Boolean hasAnnotations() {
 
     return hasAnnotations;
diff --git a/xchart/src/main/java/org/knowm/xchart/style/Theme.java b/xchart/src/main/java/org/knowm/xchart/style/Theme.java
index 2a95c81c..8c6fd817 100644
--- a/xchart/src/main/java/org/knowm/xchart/style/Theme.java
+++ b/xchart/src/main/java/org/knowm/xchart/style/Theme.java
@@ -140,6 +140,20 @@ public interface Theme extends SeriesMarkers, SeriesLines, SeriesColors {
 
   Color getToolTipHighlightColor();
 
+  // Cursor ///////////////////////////////
+
+  boolean isCursorEnabled();
+
+  Color getCursorColor();
+
+  float getCursorSize();
+
+  Font getCursorFont();
+
+  Color getCursorFontColor();
+
+  Color getCursorBackgroundColor();
+
   // Bar Charts ///////////////////////////////
 
   double getAvailableSpaceFill();


Test patch:
diff --git a/xchart-demo/src/main/java/org/knowm/xchart/demo/ExampleChartTester.java b/xchart-demo/src/main/java/org/knowm/xchart/demo/ExampleChartTester.java
index 4555310f..e1602ab8 100644
--- a/xchart-demo/src/main/java/org/knowm/xchart/demo/ExampleChartTester.java
+++ b/xchart-demo/src/main/java/org/knowm/xchart/demo/ExampleChartTester.java
@@ -61,6 +61,7 @@ import org.knowm.xchart.demo.charts.line.LineChart05;
 import org.knowm.xchart.demo.charts.line.LineChart06;
 import org.knowm.xchart.demo.charts.line.LineChart07;
 import org.knowm.xchart.demo.charts.line.LineChart08;
+import org.knowm.xchart.demo.charts.line.LineChart09;
 import org.knowm.xchart.demo.charts.ohlc.OHLCChart01;
 import org.knowm.xchart.demo.charts.ohlc.OHLCChart02;
 import org.knowm.xchart.demo.charts.ohlc.OHLCChart03;
@@ -332,6 +333,7 @@ public class ExampleChartTester extends JPanel implements TreeSelectionListener
     exList.add(new LineChart06());
     exList.add(new LineChart07());
     exList.add(new LineChart08());
+    exList.add(new LineChart09());
 
     // Scatter
     exList.add(new ScatterChart01());