mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 15:10:44 +01:00
Update chart WIP
This commit is contained in:
parent
f743bb88a8
commit
bdad86526a
9 changed files with 391 additions and 78 deletions
|
@ -73,18 +73,29 @@
|
|||
-demo-bar-fill: #cccccc;
|
||||
}
|
||||
|
||||
|
||||
.chart-plot-background {
|
||||
-fx-background-color: transparent;
|
||||
.volume-bar {
|
||||
-fx-padding: 5;
|
||||
-demo-bar-fill: #91b1cc;
|
||||
-fx-background-color: #91b1cc;
|
||||
-fx-background-insets: 0, 1, 2;
|
||||
}
|
||||
|
||||
.default-color0.chart-series-line {
|
||||
.volume-bar.bg {
|
||||
-demo-bar-fill: #91b1cc;
|
||||
}
|
||||
|
||||
.volume-bar {
|
||||
-fx-effect: dropshadow(two-pass-box, rgba(0, 0, 0, 0.4), 10, 0.0, 2, 4);
|
||||
}
|
||||
|
||||
/*.default-color0.chart-series-line {
|
||||
-fx-stroke: blue;
|
||||
}
|
||||
|
||||
.default-color0.chart-line-symbol {
|
||||
-fx-background-color: red, green;
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
.chart-alternative-row-fill {
|
||||
|
@ -97,6 +108,7 @@
|
|||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
/*
|
||||
.chart-legend {
|
||||
-fx-background-color: transparent;
|
||||
-fx-padding: 20px;
|
||||
|
@ -105,4 +117,4 @@
|
|||
.chart-legend-item-symbol {
|
||||
-fx-background-radius: 0;
|
||||
}
|
||||
|
||||
*/
|
||||
|
|
|
@ -21,7 +21,7 @@ import io.bitsquare.common.UserThread;
|
|||
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
|
||||
import io.bitsquare.gui.common.view.FxmlView;
|
||||
import io.bitsquare.gui.main.markets.trades.candlestick.CandleStickChart;
|
||||
import io.bitsquare.gui.main.markets.trades.candlestick.MyBarChart;
|
||||
import io.bitsquare.gui.main.markets.trades.candlestick.VolumeChart;
|
||||
import io.bitsquare.gui.util.BSFormatter;
|
||||
import io.bitsquare.locale.CryptoCurrency;
|
||||
import io.bitsquare.locale.FiatCurrency;
|
||||
|
@ -36,7 +36,6 @@ import javafx.collections.ListChangeListener;
|
|||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.chart.CategoryAxis;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.control.*;
|
||||
|
@ -45,6 +44,7 @@ import javafx.scene.layout.StackPane;
|
|||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.StringConverter;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.fxmisc.easybind.Subscription;
|
||||
|
@ -54,6 +54,7 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@FxmlView
|
||||
public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesChartsViewModel> {
|
||||
|
@ -70,10 +71,9 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
private ToggleGroup toggleGroup;
|
||||
|
||||
private NumberAxis timeAxisX, priceAxisY, volumeAxisY;
|
||||
private CategoryAxis volumeAxisX;
|
||||
private XYChart.Series<Number, Number> priceSeries;
|
||||
private XYChart.Series<String, Number> volumeSeries;
|
||||
private MyBarChart<String, Number> volumeChart;
|
||||
private XYChart.Series<Number, Number> volumeSeries;
|
||||
private VolumeChart volumeChart;
|
||||
private CandleStickChart priceChart;
|
||||
|
||||
private final ListChangeListener<XYChart.Data<Number, Number>> itemsChangeListener;
|
||||
|
@ -88,7 +88,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
super(model);
|
||||
this.formatter = formatter;
|
||||
|
||||
itemsChangeListener = c -> updateChartData();
|
||||
itemsChangeListener = c -> UserThread.runAfter(() -> updateChartData(), 20, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -99,7 +99,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
final VBox tableVBox = getTableBox();
|
||||
|
||||
StackPane stackPane = new StackPane();
|
||||
stackPane.getChildren().addAll(volumeChart, priceChart);
|
||||
stackPane.getChildren().addAll(priceChart, volumeChart);
|
||||
|
||||
root.getChildren().addAll(currencyHBox, toggleBarHBox, stackPane, tableVBox);
|
||||
|
||||
|
@ -157,9 +157,14 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void createChart() {
|
||||
volumeSeries = new XYChart.Series<>();
|
||||
timeAxisX = new NumberAxis(0, model.upperBound + 1, 1);
|
||||
timeAxisX.setTickUnit(1);
|
||||
timeAxisX.setMinorTickCount(0);
|
||||
timeAxisX.setForceZeroInRange(false);
|
||||
timeAxisX.setLabel("Date/Time");
|
||||
timeAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
|
||||
|
||||
volumeAxisX = new CategoryAxis();
|
||||
volumeSeries = new XYChart.Series<>();
|
||||
|
||||
volumeAxisY = new NumberAxis();
|
||||
volumeAxisY.setForceZeroInRange(false);
|
||||
|
@ -168,23 +173,20 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
volumeAxisY.setTickLabelFormatter(getVolumeStringConverter());
|
||||
volumeAxisY.setSide(Side.RIGHT);
|
||||
|
||||
volumeChart = new MyBarChart<>(volumeAxisX, volumeAxisY);
|
||||
NumberAxis volumeAxisX = new NumberAxis(0, model.upperBound + 1, 1);
|
||||
volumeAxisX.setTickLabelsVisible(false);
|
||||
volumeAxisX.setTickMarkVisible(false);
|
||||
volumeAxisX.lookup(".axis-minor-tick-mark").setOpacity(0);
|
||||
|
||||
volumeChart = new VolumeChart(volumeAxisX, volumeAxisY);
|
||||
volumeChart.setData(FXCollections.observableArrayList(volumeSeries));
|
||||
volumeChart.setAnimated(true);
|
||||
volumeChart.setAnimated(false);
|
||||
volumeChart.setMinHeight(300);
|
||||
volumeChart.setPadding(new Insets(0, 0, -10, 75));
|
||||
volumeChart.setPadding(new Insets(0, 0, 40, 69));
|
||||
volumeChart.setLegendVisible(false);
|
||||
|
||||
|
||||
priceSeries = new XYChart.Series<>();
|
||||
|
||||
timeAxisX = new NumberAxis(0, model.upperBound + 1, 1);
|
||||
timeAxisX.setTickUnit(1);
|
||||
timeAxisX.setMinorTickCount(0);
|
||||
timeAxisX.setForceZeroInRange(false);
|
||||
timeAxisX.setLabel("Date/Time");
|
||||
timeAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
|
||||
|
||||
priceAxisY = new NumberAxis();
|
||||
priceAxisY.setForceZeroInRange(false);
|
||||
priceAxisY.setAutoRanging(true);
|
||||
|
@ -197,7 +199,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
priceChart.setLegendVisible(false);
|
||||
priceChart.setAnimated(true);
|
||||
priceChart.setMinHeight(300);
|
||||
priceChart.setPadding(new Insets(0, 75, -10, 0));
|
||||
priceChart.setPadding(new Insets(0, 69, 0, 0));
|
||||
priceChart.setAlternativeRowFillVisible(false);
|
||||
priceChart.setAlternativeColumnFillVisible(false);
|
||||
priceChart.setHorizontalGridLinesVisible(false);
|
||||
|
@ -213,7 +215,11 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
priceChart.getData().clear();
|
||||
priceChart.setData(FXCollections.observableArrayList(priceSeries));
|
||||
|
||||
volumeSeries.getData().clear();
|
||||
volumeSeries = new XYChart.Series<>();
|
||||
volumeSeries.getData().setAll(model.volumeItems);
|
||||
volumeChart.getData().clear();
|
||||
volumeChart.setData(FXCollections.observableArrayList(volumeSeries));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@ -261,7 +267,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
@Override
|
||||
public String toString(Number object) {
|
||||
// comes as double
|
||||
return formatter.formatFiat(Fiat.valueOf(model.getCurrencyCode(), new Double((double) object).longValue()));
|
||||
return formatter.formatCoin(Coin.valueOf(new Double((double) object).longValue()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -60,7 +60,7 @@ class TradesChartsViewModel extends ActivatableViewModel {
|
|||
final ObjectProperty<TradeCurrency> tradeCurrency = new SimpleObjectProperty<>();
|
||||
private final HashMapChangedListener mapChangedListener;
|
||||
ObservableList<XYChart.Data<Number, Number>> priceItems = FXCollections.observableArrayList();
|
||||
ObservableList<XYChart.Data<String, Number>> volumeItems = FXCollections.observableArrayList();
|
||||
ObservableList<XYChart.Data<Number, Number>> volumeItems = FXCollections.observableArrayList();
|
||||
|
||||
private P2PService p2PService;
|
||||
final ObservableList<TradeStatistics> tradeStatistics = FXCollections.observableArrayList();
|
||||
|
@ -162,7 +162,7 @@ class TradesChartsViewModel extends ActivatableViewModel {
|
|||
.collect(Collectors.toList()));
|
||||
|
||||
volumeItems.setAll(candleDataList.stream()
|
||||
.map(e -> new XYChart.Data<String, Number>(String.valueOf(e.tick), e.open, new CandleStickExtraValues(e.close, e.high, e.low, e.average)))
|
||||
.map(e -> new XYChart.Data<Number, Number>(e.tick, e.accumulatedAmount))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
|
@ -171,17 +171,17 @@ class TradesChartsViewModel extends ActivatableViewModel {
|
|||
long close = 0;
|
||||
long high = 0;
|
||||
long low = 0;
|
||||
long volume = 0;
|
||||
long amount = 0;
|
||||
long accumulatedVolume = 0;
|
||||
long accumulatedAmount = 0;
|
||||
|
||||
for (TradeStatistics item : set) {
|
||||
final long tradePriceAsLong = item.tradePriceAsLong;
|
||||
low = (low != 0) ? Math.min(low, tradePriceAsLong) : tradePriceAsLong;
|
||||
high = (high != 0) ? Math.max(high, tradePriceAsLong) : tradePriceAsLong;
|
||||
volume += item.getTradeVolume().value;
|
||||
amount += item.tradeAmountAsLong;
|
||||
accumulatedVolume += item.getTradeVolume().value;
|
||||
accumulatedAmount += item.tradeAmountAsLong;
|
||||
}
|
||||
long averagePrice = Math.round(volume * Coin.COIN.value / amount);
|
||||
long averagePrice = Math.round(accumulatedVolume * Coin.COIN.value / accumulatedAmount);
|
||||
|
||||
List<TradeStatistics> list = new ArrayList<>(set);
|
||||
list.sort((o1, o2) -> (o1.tradeDateAsTime < o2.tradeDateAsTime ? -1 : (o1.tradeDateAsTime == o2.tradeDateAsTime ? 0 : 1)));
|
||||
|
@ -190,7 +190,7 @@ class TradesChartsViewModel extends ActivatableViewModel {
|
|||
close = list.get(list.size() - 1).tradePriceAsLong;
|
||||
}
|
||||
boolean isBullish = close > open;
|
||||
return new CandleData(tick, open, close, high, low, averagePrice, amount, volume, isBullish);
|
||||
return new CandleData(tick, open, close, high, low, averagePrice, accumulatedAmount, accumulatedVolume, isBullish);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -7,19 +7,19 @@ public class CandleData {
|
|||
public final long high;
|
||||
public final long low;
|
||||
public final long average;
|
||||
public final long amount;
|
||||
public final long volume;
|
||||
public final long accumulatedAmount;
|
||||
public final long accumulatedVolume;
|
||||
public final boolean isBullish;
|
||||
|
||||
public CandleData(long tick, long open, long close, long high, long low, long average, long amount, long volume, boolean isBullish) {
|
||||
public CandleData(long tick, long open, long close, long high, long low, long average, long accumulatedAmount, long accumulatedVolume, boolean isBullish) {
|
||||
this.tick = tick;
|
||||
this.open = open;
|
||||
this.close = close;
|
||||
this.high = high;
|
||||
this.low = low;
|
||||
this.average = average;
|
||||
this.amount = amount;
|
||||
this.volume = volume;
|
||||
this.accumulatedAmount = accumulatedAmount;
|
||||
this.accumulatedVolume = accumulatedVolume;
|
||||
this.isBullish = isBullish;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
package io.bitsquare.gui.main.markets.trades.candlestick;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.Axis;
|
||||
import javafx.scene.chart.BarChart;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MyBarChart<X, Y> extends BarChart<X, Y> {
|
||||
private static final Logger log = LoggerFactory.getLogger(MyBarChart.class);
|
||||
|
||||
public MyBarChart(Axis<X> xAxis, Axis<Y> yAxis) {
|
||||
super(xAxis, yAxis);
|
||||
}
|
||||
|
||||
public MyBarChart(Axis<X> xAxis, Axis<Y> yAxis, ObservableList<Series<X, Y>> data) {
|
||||
super(xAxis, yAxis, data);
|
||||
}
|
||||
|
||||
public MyBarChart(Axis<X> xAxis, Axis<Y> yAxis, ObservableList<Series<X, Y>> data, double categoryGap) {
|
||||
super(xAxis, yAxis, data, categoryGap);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dataItemAdded(Series<X, Y> series, int itemIndex, Data<X, Y> item) {
|
||||
if (getPlotChildren().contains(item.getNode()))
|
||||
getPlotChildren().remove(item.getNode());
|
||||
|
||||
super.dataItemAdded(series, itemIndex, item);
|
||||
}
|
||||
}
|
|
@ -69,9 +69,11 @@ public class TooltipContent extends GridPane {
|
|||
}
|
||||
|
||||
public void update(double open, double close, double high, double low) {
|
||||
openValue.setText(toolTipStringConverter.toString(open));
|
||||
closeValue.setText(toolTipStringConverter.toString(close));
|
||||
highValue.setText(toolTipStringConverter.toString(high));
|
||||
lowValue.setText(toolTipStringConverter.toString(low));
|
||||
if (toolTipStringConverter != null) {
|
||||
openValue.setText(toolTipStringConverter.toString(open));
|
||||
closeValue.setText(toolTipStringConverter.toString(close));
|
||||
highValue.setText(toolTipStringConverter.toString(high));
|
||||
lowValue.setText(toolTipStringConverter.toString(low));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (c) 2008, 2014, Oracle and/or its affiliates.
|
||||
* All rights reserved. Use is subject to license terms.
|
||||
*
|
||||
* This file is available and licensed under the following license:
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the distribution.
|
||||
* - Neither the name of Oracle Corporation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package io.bitsquare.gui.main.markets.trades.candlestick;
|
||||
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.util.StringConverter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Candle node used for drawing a candle
|
||||
*/
|
||||
public class VolumeBar extends Group {
|
||||
private static final Logger log = LoggerFactory.getLogger(VolumeBar.class);
|
||||
|
||||
private Region bar = new Region();
|
||||
private String seriesStyleClass;
|
||||
private String dataStyleClass;
|
||||
|
||||
VolumeBar(String seriesStyleClass, String dataStyleClass, StringConverter<Number> toolTipStringConverter) {
|
||||
setAutoSizeChildren(false);
|
||||
bar.setOpacity(0.7);
|
||||
getChildren().add(bar);
|
||||
this.seriesStyleClass = seriesStyleClass;
|
||||
this.dataStyleClass = dataStyleClass;
|
||||
updateStyleClasses();
|
||||
}
|
||||
|
||||
public void setSeriesAndDataStyleClasses(String seriesStyleClass, String dataStyleClass) {
|
||||
this.seriesStyleClass = seriesStyleClass;
|
||||
this.dataStyleClass = dataStyleClass;
|
||||
updateStyleClasses();
|
||||
}
|
||||
|
||||
public void update(double volume, double candleWidth) {
|
||||
updateStyleClasses();
|
||||
if (candleWidth == -1)
|
||||
candleWidth = bar.prefWidth(-1);
|
||||
|
||||
log.error("closeOffset " + volume);
|
||||
bar.resizeRelocate(-candleWidth / 2, 0, candleWidth, Math.max(5, volume));
|
||||
}
|
||||
|
||||
private void updateStyleClasses() {
|
||||
bar.getStyleClass().setAll("volume-bar", seriesStyleClass, dataStyleClass, "bg");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* Copyright (c) 2008, 2014, Oracle and/or its affiliates.
|
||||
* All rights reserved. Use is subject to license terms.
|
||||
*
|
||||
* This file is available and licensed under the following license:
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the distribution.
|
||||
* - Neither the name of Oracle Corporation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package io.bitsquare.gui.main.markets.trades.candlestick;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* A candlestick chart is a style of bar-chart used primarily to describe price movements of a security, derivative,
|
||||
* or currency over time.
|
||||
* <p>
|
||||
* The Data Y value is used for the opening price and then the close, high and low values are stored in the Data's
|
||||
* extra value property using a CandleStickExtraValues object.
|
||||
*/
|
||||
public class VolumeChart extends XYChart<Number, Number> {
|
||||
private static final Logger log = LoggerFactory.getLogger(CandleStickChart.class);
|
||||
|
||||
private StringConverter<Number> toolTipStringConverter;
|
||||
|
||||
// -------------- CONSTRUCTORS ----------------------------------------------
|
||||
|
||||
/**
|
||||
* Construct a new CandleStickChart with the given axis.
|
||||
*
|
||||
* @param xAxis The x axis to use
|
||||
* @param yAxis The y axis to use
|
||||
*/
|
||||
public VolumeChart(Axis<Number> xAxis, Axis<Number> yAxis) {
|
||||
super(xAxis, yAxis);
|
||||
}
|
||||
|
||||
// -------------- METHODS ------------------------------------------------------------------------------------------
|
||||
|
||||
public final void setToolTipStringConverter(StringConverter<Number> toolTipStringConverter) {
|
||||
this.toolTipStringConverter = toolTipStringConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 volumeBar positions
|
||||
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();
|
||||
if (itemNode instanceof VolumeBar) {
|
||||
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
|
||||
}
|
||||
volumeBar.update(y, candleWidth);
|
||||
|
||||
// position the volumeBar
|
||||
volumeBar.setLayoutX(x);
|
||||
volumeBar.setLayoutY(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
// fade in new 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()) {
|
||||
// fade out old volumeBar
|
||||
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) {
|
||||
// handle any data already in series
|
||||
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);
|
||||
// fade in new 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) {
|
||||
// remove all volumeBar nodes
|
||||
for (XYChart.Data<Number, Number> d : series.getData()) {
|
||||
final Node volumeBar = d.getNode();
|
||||
if (shouldAnimate()) {
|
||||
// fade out old volumeBar
|
||||
FadeTransition ft = new FadeTransition(Duration.millis(500), volumeBar);
|
||||
ft.setToValue(0);
|
||||
ft.setOnFinished((ActionEvent actionEvent) -> {
|
||||
getPlotChildren().remove(volumeBar);
|
||||
});
|
||||
ft.play();
|
||||
} else {
|
||||
getPlotChildren().remove(volumeBar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new VolumeBar node to represent a single data item
|
||||
*
|
||||
* @param seriesIndex The index of the series the data item is in
|
||||
* @param item The data item to create node for
|
||||
* @param itemIndex The index of the data item in the series
|
||||
* @return New volumeBar node to represent the give data item
|
||||
*/
|
||||
private Node createCandle(int seriesIndex, final XYChart.Data item, int itemIndex) {
|
||||
Node volumeBar = item.getNode();
|
||||
// check if volumeBar has already been created
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 volumeBar 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<Number> xa = getXAxis();
|
||||
final Axis<Number> ya = getYAxis();
|
||||
List<Number> xData = null;
|
||||
List<Number> yData = null;
|
||||
if (xa.isAutoRanging()) {
|
||||
xData = new ArrayList<Number>();
|
||||
}
|
||||
if (ya.isAutoRanging()) {
|
||||
yData = new ArrayList<Number>();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -62,8 +62,8 @@ public class TradesChartsViewModelTest {
|
|||
assertEquals(high, candleData.high);
|
||||
assertEquals(low, candleData.low);
|
||||
assertEquals(average, candleData.average);
|
||||
assertEquals(amount, candleData.amount);
|
||||
assertEquals(volume, candleData.volume);
|
||||
assertEquals(amount, candleData.accumulatedAmount);
|
||||
assertEquals(volume, candleData.accumulatedVolume);
|
||||
assertEquals(isBullish, candleData.isBullish);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue