/**
   * Construct a new CandleStickChart with the given axis.
   *
   * @param title The chart title
   * @param xAxis The x axis to use
   * @param yAxis The y axis to use
   * @param bars The bars to display on the chart
   * @param maxBarsToDisplay The maximum number of bars to display on the chart.
   */
  public CandleStickChart(
      String title,
      CategoryAxis xAxis,
      NumberAxis yAxis,
      List<BarData> bars,
      int maxBarsToDisplay) {
    super(xAxis, yAxis);
    this.xAxis = xAxis;
    this.yAxis = yAxis;
    this.maxBarsToDisplay = maxBarsToDisplay;

    yAxis.autoRangingProperty().set(true);
    yAxis.forceZeroInRangeProperty().setValue(Boolean.FALSE);
    setTitle(title);
    setAnimated(true);
    getStylesheets()
        .add(getClass().getResource("/styles/CandleStickChartStyles.css").toExternalForm());
    xAxis.setAnimated(true);
    yAxis.setAnimated(true);
    verticalGridLinesVisibleProperty().set(false);
    XYChart.Series<String, Number> series = new XYChart.Series<>();
    List<BarData> sublist = getSubList(bars, maxBarsToDisplay);
    for (BarData bar : sublist) {
      String label = "";
      label = sdf.format(bar.getDateTime().getTime());
      series.getData().add(new XYChart.Data<>(label, bar.getOpen(), bar));
      logger.log(Level.INFO, "Adding bar with date/time: {0}", bar.getDateTime().getTime());
      logger.log(Level.INFO, "Adding bar with price: {0}", bar.getOpen());
    }

    dataSeries = FXCollections.observableArrayList(series);

    setData(dataSeries);
    lastBar = sublist.get(sublist.size() - 1);
  }
  /**
   * Update the "Last" price of the most recent bar
   *
   * @param price The Last price of the most recent bar.
   */
  public void updateLast(double price) {
    if (lastBar != null) {
      lastBar.update(price);
      logger.log(
          Level.INFO, "Updating last bar with date/time: {0}", lastBar.getDateTime().getTime());

      int datalength = dataSeries.get(0).getData().size();
      dataSeries.get(0).getData().get(datalength - 1).setYValue(lastBar.getOpen());

      dataSeries.get(0).getData().get(datalength - 1).setExtraValue(lastBar);
      logger.log(
          Level.INFO,
          "Updating last bar with formatteddate/time: {0}",
          dataSeries.get(0).getData().get(datalength - 1).getXValue());
    }
  }
 /**
  * This is called when the range has been invalidated and we need to update it. If the axis are
  * auto ranging then we compile a list of all data that the given axis has to plot and call
  * invalidateRange() on the axis passing it that data.
  */
 @Override
 protected void updateAxisRange() {
   // For candle stick chart we need to override this method as we need to let the axis know that
   // they need to be able
   // to cover the whole area occupied by the high to low range not just its center data value
   final Axis<String> xa = getXAxis();
   final Axis<Number> ya = getYAxis();
   List<String> xData = null;
   List<Number> yData = null;
   if (xa.isAutoRanging()) {
     xData = new ArrayList<>();
   }
   if (ya.isAutoRanging()) {
     yData = new ArrayList<>();
   }
   if (xData != null || yData != null) {
     for (Series<String, Number> series : getData()) {
       for (Data<String, Number> data : series.getData()) {
         if (xData != null) {
           xData.add(data.getXValue());
         }
         if (yData != null) {
           BarData extras = (BarData) data.getExtraValue();
           if (extras != null) {
             yData.add(extras.getHigh());
             yData.add(extras.getLow());
           } else {
             yData.add(data.getYValue());
           }
         }
       }
     }
     if (xData != null) {
       xa.invalidateRange(xData);
     }
     if (yData != null) {
       ya.invalidateRange(yData);
     }
   }
 }
  /**
   * Appends a new bar on to the end of the chart.
   *
   * @param bar The bar to append to the chart
   */
  public void addBar(BarData bar) {

    if (dataSeries.get(0).getData().size() >= maxBarsToDisplay) {
      dataSeries.get(0).getData().remove(0);
    }

    int datalength = dataSeries.get(0).getData().size();
    dataSeries.get(0).getData().get(datalength - 1).setYValue(bar.getOpen());
    dataSeries.get(0).getData().get(datalength - 1).setExtraValue(bar);
    String label = sdf.format(bar.getDateTime().getTime());
    logger.log(Level.INFO, "Adding bar with actual time:  {0}", bar.getDateTime().getTime());
    logger.log(Level.INFO, "Adding bar with formated time: {0}", label);

    lastBar =
        new BarData(
            bar.getDateTime(), bar.getClose(), bar.getClose(), bar.getClose(), bar.getClose(), 0);
    Data<String, Number> data = new XYChart.Data<>(label, lastBar.getOpen(), lastBar);
    dataSeries.get(0).getData().add(data);
  }
  /** Called to update and layout the content for the plot */
  @Override
  protected void layoutPlotChildren() {
    // we have nothing to layout if no data is present
    if (getData() == null) {
      return;
    }
    // update candle positions
    for (int seriesIndex = 0; seriesIndex < getData().size(); seriesIndex++) {
      Series<String, Number> series = getData().get(seriesIndex);
      Iterator<Data<String, Number>> iter = getDisplayedDataIterator(series);
      Path seriesPath = null;
      if (series.getNode() instanceof Path) {
        seriesPath = (Path) series.getNode();
        seriesPath.getElements().clear();
      }
      while (iter.hasNext()) {
        Data<String, Number> item = iter.next();
        double x = getXAxis().getDisplayPosition(getCurrentDisplayedXValue(item));
        double y = getYAxis().getDisplayPosition(getCurrentDisplayedYValue(item));
        Node itemNode = item.getNode();
        BarData bar = (BarData) item.getExtraValue();
        if (itemNode instanceof Candle && item.getYValue() != null) {
          Candle candle = (Candle) itemNode;

          double close = getYAxis().getDisplayPosition(bar.getClose());
          double high = getYAxis().getDisplayPosition(bar.getHigh());
          double low = getYAxis().getDisplayPosition(bar.getLow());
          double candleWidth = 10;
          // update candle
          candle.update(close - y, high - y, low - y, candleWidth);

          // update tooltip content
          candle.updateTooltip(bar.getOpen(), bar.getClose(), bar.getHigh(), bar.getLow());

          // position the candle
          candle.setLayoutX(x);
          candle.setLayoutY(y);
        }
      }
    }
  }