/* * This file is part of Bitsquare. * * Bitsquare is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * Bitsquare is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public * License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Bitsquare. If not, see <http://www.gnu.org/licenses/>. */ package io.bitsquare.gui.main.market.trades.charts.volume; import io.bitsquare.gui.main.market.trades.charts.CandleData; import io.bitsquare.gui.main.market.trades.charts.price.CandleStickChart; import javafx.animation.FadeTransition; import javafx.event.ActionEvent; import javafx.scene.Node; import javafx.scene.chart.Axis; import javafx.scene.chart.NumberAxis; import javafx.scene.chart.XYChart; import javafx.util.Duration; import javafx.util.StringConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class VolumeChart extends XYChart<Number, Number> { private static final Logger log = LoggerFactory.getLogger(CandleStickChart.class); private StringConverter<Number> toolTipStringConverter; public VolumeChart(Axis<Number> xAxis, Axis<Number> yAxis, StringConverter<Number> toolTipStringConverter) { super(xAxis, yAxis); this.toolTipStringConverter = toolTipStringConverter; } @Override protected void layoutPlotChildren() { if (getData() == null) { return; } for (int seriesIndex = 0; seriesIndex < getData().size(); seriesIndex++) { XYChart.Series<Number, Number> series = getData().get(seriesIndex); Iterator<XYChart.Data<Number, Number>> iter = getDisplayedDataIterator(series); while (iter.hasNext()) { XYChart.Data<Number, Number> item = iter.next(); double x = getXAxis().getDisplayPosition(getCurrentDisplayedXValue(item)); double y = getYAxis().getDisplayPosition(getCurrentDisplayedYValue(item)); Node itemNode = item.getNode(); CandleData candleData = (CandleData) item.getExtraValue(); if (itemNode instanceof VolumeBar && candleData != null) { VolumeBar volumeBar = (VolumeBar) itemNode; double candleWidth = -1; if (getXAxis() instanceof NumberAxis) { NumberAxis xa = (NumberAxis) getXAxis(); candleWidth = xa.getDisplayPosition(xa.getTickUnit()) * 0.90; // use 90% width between ticks } // 97 is visible chart data height if chart height is 140. // So we subtract 43 form the height to get the height for the bar to the bottom. // Did not find a way how to request the chart data height final double height = getHeight() - 43; double upperYPos = Math.min(height - 5, y); // We want min 5px height to allow tooltips volumeBar.update(height - upperYPos, candleWidth, candleData); volumeBar.setLayoutX(x); volumeBar.setLayoutY(upperYPos); } } } } @Override protected void dataItemChanged(XYChart.Data<Number, Number> item) { } @Override protected void dataItemAdded(XYChart.Series<Number, Number> series, int itemIndex, XYChart.Data<Number, Number> item) { Node volumeBar = createCandle(getData().indexOf(series), item, itemIndex); if (getPlotChildren().contains(volumeBar)) getPlotChildren().remove(volumeBar); if (shouldAnimate()) { volumeBar.setOpacity(0); getPlotChildren().add(volumeBar); FadeTransition ft = new FadeTransition(Duration.millis(500), volumeBar); ft.setToValue(1); ft.play(); } else { getPlotChildren().add(volumeBar); } } @Override protected void dataItemRemoved(XYChart.Data<Number, Number> item, XYChart.Series<Number, Number> series) { final Node node = item.getNode(); if (shouldAnimate()) { FadeTransition ft = new FadeTransition(Duration.millis(500), node); ft.setToValue(0); ft.setOnFinished((ActionEvent actionEvent) -> getPlotChildren().remove(node)); ft.play(); } else { getPlotChildren().remove(node); } } @Override protected void seriesAdded(XYChart.Series<Number, Number> series, int seriesIndex) { for (int j = 0; j < series.getData().size(); j++) { XYChart.Data item = series.getData().get(j); Node volumeBar = createCandle(seriesIndex, item, j); if (shouldAnimate()) { volumeBar.setOpacity(0); getPlotChildren().add(volumeBar); FadeTransition ft = new FadeTransition(Duration.millis(500), volumeBar); ft.setToValue(1); ft.play(); } else { getPlotChildren().add(volumeBar); } } } @Override protected void seriesRemoved(XYChart.Series<Number, Number> series) { for (XYChart.Data<Number, Number> d : series.getData()) { final Node volumeBar = d.getNode(); if (shouldAnimate()) { FadeTransition ft = new FadeTransition(Duration.millis(500), volumeBar); ft.setToValue(0); ft.setOnFinished((ActionEvent actionEvent) -> getPlotChildren().remove(volumeBar)); ft.play(); } else { getPlotChildren().remove(volumeBar); } } } private Node createCandle(int seriesIndex, final XYChart.Data item, int itemIndex) { Node volumeBar = item.getNode(); if (volumeBar instanceof VolumeBar) { ((VolumeBar) volumeBar).setSeriesAndDataStyleClasses("series" + seriesIndex, "data" + itemIndex); } else { volumeBar = new VolumeBar("series" + seriesIndex, "data" + itemIndex, toolTipStringConverter); item.setNode(volumeBar); } return volumeBar; } @Override protected void updateAxisRange() { final Axis<Number> xa = getXAxis(); final Axis<Number> ya = getYAxis(); List<Number> xData = null; List<Number> yData = null; if (xa.isAutoRanging()) { xData = new ArrayList<>(); } if (ya.isAutoRanging()) yData = new ArrayList<>(); if (xData != null || yData != null) { for (XYChart.Series<Number, Number> series : getData()) { for (XYChart.Data<Number, Number> data : series.getData()) { if (xData != null) { xData.add(data.getXValue()); } if (yData != null) yData.add(data.getYValue()); } } if (xData != null) { xa.invalidateRange(xData); } if (yData != null) { ya.invalidateRange(yData); } } } }