Separate volume chart.

This commit is contained in:
Manfred Karrer 2016-07-24 22:01:24 +02:00
parent bdad86526a
commit 86273768b1
12 changed files with 290 additions and 213 deletions

View file

@ -119,8 +119,11 @@ public final class Preferences implements Persistable {
private double maxPriceDistanceInPercent;
private boolean useInvertedMarketPrice;
private String marketScreenCurrencyCode = CurrencyUtil.getDefaultTradeCurrency().getCode();
private String tradeStatisticsScreenCurrencyCode = CurrencyUtil.getDefaultTradeCurrency().getCode();
private String buyScreenCurrencyCode = CurrencyUtil.getDefaultTradeCurrency().getCode();
private String sellScreenCurrencyCode = CurrencyUtil.getDefaultTradeCurrency().getCode();
private int tradeStatisticsTickUnit = 0;
private boolean useStickyMarketPrice = false;
private boolean usePercentageBasedPrice = false;
private Map<String, String> peerTagMap = new HashMap<>();
@ -205,6 +208,8 @@ public final class Preferences implements Persistable {
marketScreenCurrencyCode = persisted.getMarketScreenCurrencyCode();
buyScreenCurrencyCode = persisted.getBuyScreenCurrencyCode();
sellScreenCurrencyCode = persisted.getSellScreenCurrencyCode();
tradeStatisticsScreenCurrencyCode = persisted.getTradeStatisticsScreenCurrencyCode();
tradeStatisticsTickUnit = persisted.getTradeStatisticsTickUnit();
if (persisted.getIgnoreTradersList() != null)
ignoreTradersList = persisted.getIgnoreTradersList();
@ -455,6 +460,16 @@ public final class Preferences implements Persistable {
storage.queueUpForSave();
}
public void setTradeStatisticsScreenCurrencyCode(String tradeStatisticsScreenCurrencyCode) {
this.tradeStatisticsScreenCurrencyCode = tradeStatisticsScreenCurrencyCode;
storage.queueUpForSave();
}
public void setTradeStatisticsTickUnit(int tradeStatisticsTickUnit) {
this.tradeStatisticsTickUnit = tradeStatisticsTickUnit;
storage.queueUpForSave();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getter
@ -607,6 +622,15 @@ public final class Preferences implements Persistable {
public String getDefaultPath() {
return defaultPath;
}
public String getTradeStatisticsScreenCurrencyCode() {
return tradeStatisticsScreenCurrencyCode;
}
public int getTradeStatisticsTickUnit() {
return tradeStatisticsTickUnit;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
@ -636,4 +660,5 @@ public final class Preferences implements Persistable {
this.blockChainExplorerMainNet = blockChainExplorerMainNet;
storage.queueUpForSave(2000);
}
}

View file

@ -81,7 +81,7 @@
}
.volume-bar.bg {
-demo-bar-fill: #91b1cc;
-demo-bar-fill: #70bfc6;
}
.volume-bar {

View file

@ -21,7 +21,7 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.*?>
<VBox fx:id="root" fx:controller="io.bitsquare.gui.main.markets.trades.TradesChartsView"
spacing="20.0" fillWidth="true"
spacing="10.0" fillWidth="true"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">

View file

@ -33,14 +33,15 @@ import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.Side;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import javafx.util.StringConverter;
@ -77,6 +78,12 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
private CandleStickChart priceChart;
private final ListChangeListener<XYChart.Data<Number, Number>> itemsChangeListener;
private double priceAxisYWidth;
private double volumeAxisYWidth;
private ChangeListener<Number> priceAxisYWidthListener;
private ChangeListener<Number> volumeAxisYWidthListener;
private NumberAxis volumeAxisX;
private SortedList<TradeStatistics> sortedList;
///////////////////////////////////////////////////////////////////////////////////////////
@ -94,14 +101,15 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
@Override
public void initialize() {
HBox currencyHBox = createCurrencyComboBox();
HBox toggleBarHBox = createToggleBar();
// HBox toggleBarHBox = createToggleBar();
createChart();
final VBox tableVBox = getTableBox();
createTableBox();
StackPane stackPane = new StackPane();
stackPane.getChildren().addAll(priceChart, volumeChart);
/* StackPane stackPane = new StackPane();
stackPane.getChildren().addAll(volumeChart, priceChart);*/
root.getChildren().addAll(currencyHBox, toggleBarHBox, stackPane, tableVBox);
root.getChildren().addAll(currencyHBox, priceChart, volumeChart, tableView);
toggleChangeListener = (observable, oldValue, newValue) -> {
if (newValue != null) {
@ -109,6 +117,14 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
timeAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
}
};
priceAxisYWidthListener = (observable, oldValue, newValue) -> {
priceAxisYWidth = (double) newValue;
layoutChart();
};
volumeAxisYWidthListener = (observable, oldValue, newValue) -> {
volumeAxisYWidth = (double) newValue;
layoutChart();
};
}
@ -119,8 +135,14 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
currencyComboBox.setVisibleRowCount(Math.min(currencyComboBox.getItems().size(), 25));
currencyComboBox.setOnAction(e -> model.onSetTradeCurrency(currencyComboBox.getSelectionModel().getSelectedItem()));
toggleGroup.getToggles().get(model.tickUnit.ordinal()).setSelected(true);
model.priceItems.addListener(itemsChangeListener);
tradeCurrencySubscriber = EasyBind.subscribe(model.tradeCurrency,
toggleGroup.selectedToggleProperty().addListener(toggleChangeListener);
priceAxisY.widthProperty().addListener(priceAxisYWidthListener);
volumeAxisY.widthProperty().addListener(volumeAxisYWidthListener);
tradeCurrencySubscriber = EasyBind.subscribe(model.tradeCurrencyProperty,
tradeCurrency -> {
String code = tradeCurrency.getCode();
String tradeCurrencyName = tradeCurrency.getName();
@ -135,8 +157,10 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
volumeAxisY.setLabel(volumeColumnLabel.get());
});
tableView.setItems(model.tradeStatistics);
toggleGroup.selectedToggleProperty().addListener(toggleChangeListener);
sortedList = new SortedList<>(model.tradeStatisticsByCurrency);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
updateChartData();
timeAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
}
@ -144,11 +168,15 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
@Override
protected void deactivate() {
model.priceItems.removeListener(itemsChangeListener);
toggleGroup.selectedToggleProperty().removeListener(toggleChangeListener);
priceAxisY.widthProperty().removeListener(priceAxisYWidthListener);
volumeAxisY.widthProperty().removeListener(volumeAxisYWidthListener);
tradeCurrencySubscriber.unsubscribe();
currencyComboBox.setOnAction(null);
toggleGroup.selectedToggleProperty().removeListener(toggleChangeListener);
priceSeries.getData().clear();
priceChart.getData().clear();
sortedList.comparatorProperty().unbind();
}
@ -161,65 +189,109 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
timeAxisX.setTickUnit(1);
timeAxisX.setMinorTickCount(0);
timeAxisX.setForceZeroInRange(false);
timeAxisX.setLabel("Date/Time");
timeAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
volumeSeries = new XYChart.Series<>();
volumeAxisY = new NumberAxis();
volumeAxisY.setForceZeroInRange(false);
volumeAxisY.setAutoRanging(true);
volumeAxisY.setLabel("Volume");
volumeAxisY.setTickLabelFormatter(getVolumeStringConverter());
volumeAxisY.setSide(Side.RIGHT);
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(false);
volumeChart.setMinHeight(300);
volumeChart.setPadding(new Insets(0, 0, 40, 69));
volumeChart.setLegendVisible(false);
priceSeries = new XYChart.Series<>();
priceAxisY = new NumberAxis();
priceAxisY.setForceZeroInRange(false);
priceAxisY.setAutoRanging(true);
priceAxisY.setLabel(priceColumnLabel.get());
priceAxisY.setTickLabelFormatter(getPriceStringConverter());
priceAxisY.setTickLabelFormatter(new StringConverter<Number>() {
@Override
public String toString(Number object) {
return formatter.formatFiat(Fiat.valueOf(model.getCurrencyCode(), new Double((double) object).longValue()));
}
@Override
public Number fromString(String string) {
return null;
}
});
priceChart = new CandleStickChart(timeAxisX, priceAxisY);
priceChart.setData(FXCollections.observableArrayList(priceSeries));
priceChart.setToolTipStringConverter(getPriceStringConverter());
priceChart.setLegendVisible(false);
priceChart.setToolTipStringConverter(new StringConverter<Number>() {
@Override
public String toString(Number object) {
return formatter.formatFiatWithCode(Fiat.valueOf(model.getCurrencyCode(), new Double((double) object).longValue()));
}
@Override
public Number fromString(String string) {
return null;
}
});
priceChart.setAnimated(true);
priceChart.setMinHeight(300);
priceChart.setPadding(new Insets(0, 69, 0, 0));
priceChart.setAlternativeRowFillVisible(false);
priceChart.setAlternativeColumnFillVisible(false);
priceChart.setHorizontalGridLinesVisible(false);
priceChart.setVerticalGridLinesVisible(false);
priceChart.getXAxis().setVisible(false);
priceChart.getYAxis().setVisible(false);
priceChart.setMinHeight(250);
priceChart.setLegendVisible(false);
volumeSeries = new XYChart.Series<>();
volumeAxisY = new NumberAxis();
volumeAxisY.setForceZeroInRange(true);
volumeAxisY.setAutoRanging(true);
volumeAxisY.setLabel("Volume");
volumeAxisY.setTickLabelFormatter(new StringConverter<Number>() {
@Override
public String toString(Number object) {
return formatter.formatCoin(Coin.valueOf(new Double((double) object).longValue()));
}
@Override
public Number fromString(String string) {
return null;
}
});
volumeAxisX = new NumberAxis(0, model.upperBound + 1, 1);
volumeAxisX.setTickUnit(1);
volumeAxisX.setMinorTickCount(0);
volumeAxisX.setForceZeroInRange(false);
volumeAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
volumeChart = new VolumeChart(volumeAxisX, volumeAxisY);
volumeChart.setData(FXCollections.observableArrayList(volumeSeries));
volumeChart.setToolTipStringConverter(new StringConverter<Number>() {
@Override
public String toString(Number object) {
return formatter.formatCoinWithCode(Coin.valueOf(new Double((double) object).longValue()));
}
@Override
public Number fromString(String string) {
return null;
}
});
volumeChart.setAnimated(true);
volumeChart.setMinHeight(140);
volumeChart.setLegendVisible(false);
}
private void updateChartData() {
priceSeries.getData().clear();
priceSeries = new XYChart.Series<>();
priceSeries.getData().setAll(model.priceItems);
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));
priceSeries.getData().clear();
priceSeries = new XYChart.Series<>();
priceSeries.getData().setAll(model.priceItems);
priceChart.getData().clear();
priceChart.setData(FXCollections.observableArrayList(priceSeries));
}
private void layoutChart() {
UserThread.execute(() -> {
if (volumeAxisYWidth > priceAxisYWidth) {
priceChart.setPadding(new Insets(0, 0, 0, volumeAxisYWidth - priceAxisYWidth));
volumeChart.setPadding(new Insets(0, 0, 0, 0));
} else if (volumeAxisYWidth < priceAxisYWidth) {
priceChart.setPadding(new Insets(0, 0, 0, 0));
volumeChart.setPadding(new Insets(0, 0, 0, priceAxisYWidth - volumeAxisYWidth));
}
});
}
@NotNull
@ -245,44 +317,15 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
};
}
@NotNull
private StringConverter<Number> getPriceStringConverter() {
return new StringConverter<Number>() {
@Override
public String toString(Number object) {
// comes as double
return formatter.formatFiat(Fiat.valueOf(model.getCurrencyCode(), new Double((double) object).longValue()));
}
@Override
public Number fromString(String string) {
return null;
}
};
}
@NotNull
private StringConverter<Number> getVolumeStringConverter() {
return new StringConverter<Number>() {
@Override
public String toString(Number object) {
// comes as double
return formatter.formatCoin(Coin.valueOf(new Double((double) object).longValue()));
}
@Override
public Number fromString(String string) {
return null;
}
};
}
///////////////////////////////////////////////////////////////////////////////////////////
// CurrencyComboBox
///////////////////////////////////////////////////////////////////////////////////////////
private HBox createCurrencyComboBox() {
Label currencyLabel = new Label("Currency:");
currencyLabel.setPadding(new Insets(0, 3, 0, 0));
currencyComboBox = new ComboBox<>();
currencyComboBox.setPromptText("Select currency");
currencyComboBox.setConverter(new StringConverter<TradeCurrency>() {
@ -303,29 +346,11 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
}
});
Label currencyLabel = new Label("Currency:");
HBox currencyHBox = new HBox();
currencyHBox.setSpacing(5);
currencyHBox.setPadding(new Insets(10, -20, 0, 20));
currencyHBox.setAlignment(Pos.CENTER_LEFT);
currencyHBox.getChildren().addAll(currencyLabel, currencyComboBox);
return currencyHBox;
}
///////////////////////////////////////////////////////////////////////////////////////////
// ToggleBar
///////////////////////////////////////////////////////////////////////////////////////////
private HBox createToggleBar() {
HBox hBox = new HBox();
hBox.setSpacing(0);
hBox.setPadding(new Insets(0, 0, -26, 85));
Pane spacer = new Pane();
HBox.setHgrow(spacer, Priority.ALWAYS);
Label label = new Label("Interval:");
label.setPadding(new Insets(5, 5, 0, 0));
label.setPadding(new Insets(0, 3, 0, 0));
toggleGroup = new ToggleGroup();
ToggleButton month = getToggleButton("Month", TradesChartsViewModel.TickUnit.MONTH, toggleGroup, "toggle-left");
@ -334,8 +359,12 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
ToggleButton hour = getToggleButton("Hour", TradesChartsViewModel.TickUnit.HOUR, toggleGroup, "toggle-center");
ToggleButton minute10 = getToggleButton("10 Minute", TradesChartsViewModel.TickUnit.MINUTE_10, toggleGroup, "toggle-center");
ToggleButton minute = getToggleButton("Minute", TradesChartsViewModel.TickUnit.MINUTE, toggleGroup, "toggle-right");
minute10.setSelected(true);
hBox.getChildren().addAll(label, month, week, day, hour, minute10, minute);
HBox hBox = new HBox();
hBox.setSpacing(0);
hBox.setPadding(new Insets(5, 20, -10, 8));
hBox.setAlignment(Pos.CENTER_LEFT);
hBox.getChildren().addAll(currencyLabel, currencyComboBox, spacer, label, month, week, day, hour, minute10, minute);
return hBox;
}
@ -353,8 +382,9 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
// Table
///////////////////////////////////////////////////////////////////////////////////////////
private VBox getTableBox() {
private void createTableBox() {
tableView = new TableView<>();
tableView.setMinHeight(120);
// date
TableColumn<TradeStatistics, TradeStatistics> dateColumn = new TableColumn<>("Date/Time");
@ -377,6 +407,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
};
}
});
dateColumn.setComparator((o1, o2) -> o1.getTradeDate().compareTo(o2.getTradeDate()));
tableView.getColumns().add(dateColumn);
@ -401,6 +432,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
};
}
});
amountColumn.setComparator((o1, o2) -> o1.getTradeAmount().compareTo(o2.getTradeAmount()));
tableView.getColumns().add(amountColumn);
@ -426,6 +458,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
};
}
});
priceColumn.setComparator((o1, o2) -> o1.getTradePrice().compareTo(o2.getTradePrice()));
tableView.getColumns().add(priceColumn);
@ -451,6 +484,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
};
}
});
volumeColumn.setComparator((o1, o2) -> o1.getTradeVolume().compareTo(o2.getTradeVolume()));
tableView.getColumns().add(volumeColumn);
@ -475,24 +509,15 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
};
}
});
directionColumn.setComparator((o1, o2) -> o1.offer.getDirection().compareTo(o2.offer.getDirection()));
tableView.getColumns().add(directionColumn);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
Label placeholder = new Label("Currently there is no data available");
placeholder.setWrapText(true);
tableView.setPlaceholder(placeholder);
Label titleLabel = new Label("Trades");
titleLabel.setStyle("-fx-font-weight: bold; -fx-font-size: 16; -fx-alignment: center");
UserThread.execute(() -> titleLabel.prefWidthProperty().bind(tableView.widthProperty()));
VBox vBox = new VBox();
vBox.setSpacing(10);
vBox.setFillWidth(true);
vBox.setMinHeight(190);
vBox.getChildren().addAll(titleLabel, tableView);
return vBox;
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn);
}
}

View file

@ -42,7 +42,6 @@ import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
class TradesChartsViewModel extends ActivatableViewModel {
private static final Logger log = LoggerFactory.getLogger(TradesChartsViewModel.class);
@ -57,16 +56,20 @@ class TradesChartsViewModel extends ActivatableViewModel {
}
private final Preferences preferences;
final ObjectProperty<TradeCurrency> tradeCurrency = new SimpleObjectProperty<>();
private P2PService p2PService;
private final HashMapChangedListener mapChangedListener;
final ObjectProperty<TradeCurrency> tradeCurrencyProperty = new SimpleObjectProperty<>();
private final Set<TradeStatistics> allTradeStatistics = new HashSet<>();
final ObservableList<TradeStatistics> tradeStatisticsByCurrency = FXCollections.observableArrayList();
ObservableList<XYChart.Data<Number, Number>> priceItems = FXCollections.observableArrayList();
ObservableList<XYChart.Data<Number, Number>> volumeItems = FXCollections.observableArrayList();
private P2PService p2PService;
final ObservableList<TradeStatistics> tradeStatistics = FXCollections.observableArrayList();
TickUnit tickUnit = TickUnit.MINUTE_10;
TickUnit tickUnit;
int upperBound = 30;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@ -85,19 +88,21 @@ class TradesChartsViewModel extends ActivatableViewModel {
@Override
public void onRemoved(ProtectedStorageEntry data) {
final StoragePayload storagePayload = data.getStoragePayload();
if (storagePayload instanceof TradeStatistics && tradeStatistics.contains(storagePayload)) {
tradeStatistics.remove(storagePayload);
if (storagePayload instanceof TradeStatistics && allTradeStatistics.contains(storagePayload)) {
allTradeStatistics.remove(storagePayload);
updateChartData();
}
}
};
Optional<TradeCurrency> tradeCurrencyOptional = CurrencyUtil.getTradeCurrency(preferences.getMarketScreenCurrencyCode());
Optional<TradeCurrency> tradeCurrencyOptional = CurrencyUtil.getTradeCurrency(preferences.getTradeStatisticsScreenCurrencyCode());
if (tradeCurrencyOptional.isPresent())
tradeCurrency.set(tradeCurrencyOptional.get());
tradeCurrencyProperty.set(tradeCurrencyOptional.get());
else {
tradeCurrency.set(CurrencyUtil.getDefaultTradeCurrency());
tradeCurrencyProperty.set(CurrencyUtil.getDefaultTradeCurrency());
}
tickUnit = TickUnit.values()[preferences.getTradeStatisticsTickUnit()];
}
@VisibleForTesting
@ -106,13 +111,6 @@ class TradesChartsViewModel extends ActivatableViewModel {
preferences = null;
}
private void addItem(StoragePayload storagePayload, boolean doUpdate) {
if (storagePayload instanceof TradeStatistics && !tradeStatistics.contains(storagePayload)) {
tradeStatistics.add((TradeStatistics) storagePayload);
if (doUpdate)
updateChartData();
}
}
@Override
protected void activate() {
@ -126,18 +124,61 @@ class TradesChartsViewModel extends ActivatableViewModel {
p2PService.removeHashMapChangedListener(mapChangedListener);
}
public void setTickUnit(TickUnit tickUnit) {
this.tickUnit = tickUnit;
///////////////////////////////////////////////////////////////////////////////////////////
// UI actions
///////////////////////////////////////////////////////////////////////////////////////////
public void onSetTradeCurrency(TradeCurrency tradeCurrency) {
this.tradeCurrencyProperty.set(tradeCurrency);
preferences.setTradeStatisticsScreenCurrencyCode(tradeCurrency.getCode());
updateChartData();
}
public void setTickUnit(TickUnit tickUnit) {
this.tickUnit = tickUnit;
preferences.setTradeStatisticsTickUnit(tickUnit.ordinal());
updateChartData();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public String getCurrencyCode() {
return tradeCurrencyProperty.get().getCode();
}
public ObservableList<TradeCurrency> getTradeCurrencies() {
return preferences.getTradeCurrenciesAsObservable();
}
public TradeCurrency getTradeCurrency() {
return tradeCurrencyProperty.get();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void addItem(StoragePayload storagePayload, boolean doUpdate) {
if (storagePayload instanceof TradeStatistics && !allTradeStatistics.contains(storagePayload)) {
allTradeStatistics.add((TradeStatistics) storagePayload);
if (doUpdate)
updateChartData();
}
}
private void updateChartData() {
final Stream<TradeStatistics> tradeStatisticsStream = tradeStatistics.stream()
.filter(e -> e.offer.getCurrencyCode().equals(getCurrencyCode()));
tradeStatisticsByCurrency.setAll(allTradeStatistics.stream()
.filter(e -> e.offer.getCurrencyCode().equals(getCurrencyCode()))
.collect(Collectors.toList()));
// Get all entries for the defined time interval
Map<Long, Set<TradeStatistics>> itemsPerInterval = new HashMap<>();
tradeStatisticsStream.forEach(e -> {
tradeStatisticsByCurrency.stream().forEach(e -> {
Set<TradeStatistics> set;
final long time = getTickFromTime(e.tradeDateAsTime, tickUnit);
final long now = getTickFromTime(new Date().getTime(), tickUnit);
@ -158,11 +199,11 @@ class TradesChartsViewModel extends ActivatableViewModel {
candleDataList.sort((o1, o2) -> (o1.tick < o2.tick ? -1 : (o1.tick == o2.tick ? 0 : 1)));
priceItems.setAll(candleDataList.stream()
.map(e -> new XYChart.Data<Number, Number>(e.tick, e.open, new CandleStickExtraValues(e.close, e.high, e.low, e.average)))
.map(e -> new XYChart.Data<Number, Number>(e.tick, e.open, new CandleStickExtraValues(e.close, e.high, e.low, e.average, e.accumulatedAmount)))
.collect(Collectors.toList()));
volumeItems.setAll(candleDataList.stream()
.map(e -> new XYChart.Data<Number, Number>(e.tick, e.accumulatedAmount))
.map(e -> new XYChart.Data<Number, Number>(e.tick, e.accumulatedAmount, new CandleStickExtraValues(e.close, e.high, e.low, e.average, e.accumulatedAmount)))
.collect(Collectors.toList()));
}
@ -232,31 +273,4 @@ class TradesChartsViewModel extends ActivatableViewModel {
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI actions
///////////////////////////////////////////////////////////////////////////////////////////
public void onSetTradeCurrency(TradeCurrency tradeCurrency) {
this.tradeCurrency.set(tradeCurrency);
updateChartData();
//preferences.setMarketScreenCurrencyCode(tradeCurrency.getCode());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public String getCurrencyCode() {
return tradeCurrency.get().getCode();
}
public ObservableList<TradeCurrency> getTradeCurrencies() {
return preferences.getTradeCurrenciesAsObservable();
}
public TradeCurrency getTradeCurrency() {
return tradeCurrency.get();
}
}

View file

@ -54,15 +54,15 @@ public class Candle extends Group {
private Tooltip tooltip = new Tooltip();
private double closeOffset;
Candle(String seriesStyleClass, String dataStyleClass, StringConverter<Number> toolTipStringConverter) {
Candle(String seriesStyleClass, String dataStyleClass, StringConverter<Number> priceStringConverter) {
setAutoSizeChildren(false);
getChildren().addAll(highLowLine, bar);
this.seriesStyleClass = seriesStyleClass;
this.dataStyleClass = dataStyleClass;
updateStyleClasses();
tooltipContent = new TooltipContent(toolTipStringConverter);
tooltipContent = new TooltipContent(priceStringConverter);
tooltip.setGraphic(tooltipContent);
Tooltip.install(bar, tooltip);
Tooltip.install(this, tooltip);
}
public void setSeriesAndDataStyleClasses(String seriesStyleClass, String dataStyleClass) {

View file

@ -59,7 +59,7 @@ import java.util.List;
public class CandleStickChart extends XYChart<Number, Number> {
private static final Logger log = LoggerFactory.getLogger(CandleStickChart.class);
private StringConverter<Number> toolTipStringConverter;
private StringConverter<Number> priceStringConverter;
private Path seriesPath;
// -------------- CONSTRUCTORS ----------------------------------------------
@ -76,8 +76,8 @@ public class CandleStickChart extends XYChart<Number, Number> {
// -------------- METHODS ------------------------------------------------------------------------------------------
public final void setToolTipStringConverter(StringConverter<Number> toolTipStringConverter) {
this.toolTipStringConverter = toolTipStringConverter;
public final void setToolTipStringConverter(StringConverter<Number> priceStringConverter) {
this.priceStringConverter = priceStringConverter;
}
/**
@ -239,7 +239,7 @@ public class CandleStickChart extends XYChart<Number, Number> {
if (candle instanceof Candle) {
((Candle) candle).setSeriesAndDataStyleClasses("series" + seriesIndex, "data" + itemIndex);
} else {
candle = new Candle("series" + seriesIndex, "data" + itemIndex, toolTipStringConverter);
candle = new Candle("series" + seriesIndex, "data" + itemIndex, priceStringConverter);
item.setNode(candle);
}
return candle;

View file

@ -93,7 +93,7 @@ public class CandleStickChartApp extends Application {
XYChart.Series<Number, Number> series = new XYChart.Series<>();
for (int i = 0; i < data.length; i++) {
double[] day = data[i];
series.getData().add(new XYChart.Data<>(day[0], day[1], new CandleStickExtraValues(day[2], day[3], day[4], day[5])));
series.getData().add(new XYChart.Data<>(day[0], day[1], new CandleStickExtraValues(day[2], day[3], day[4], day[5], day[5])));
}
ObservableList<XYChart.Series<Number, Number>> data = chart.getData();
if (data == null) {

View file

@ -39,12 +39,14 @@ public class CandleStickExtraValues {
private double high;
private double low;
private double average;
private double volume;
public CandleStickExtraValues(double close, double high, double low, double average) {
public CandleStickExtraValues(double close, double high, double low, double average, double volume) {
this.close = close;
this.high = high;
this.low = low;
this.average = average;
this.volume = volume;
}
public double getClose() {
@ -59,12 +61,16 @@ public class CandleStickExtraValues {
return low;
}
public double getVolume() {
return volume;
}
public double getAverage() {
return average;
}
@Override
public String toString() {
return "CandleStickExtraValues{" + "close=" + close + ", high=" + high + ", low=" + low + ", average=" + average + '}';
return "CandleStickExtraValues{" + "close=" + close + ", high=" + high + ", low=" + low + ", average=" + average + ", volume=" + volume + '}';
}
}

View file

@ -31,7 +31,6 @@
*/
package io.bitsquare.gui.main.markets.trades.candlestick;
import io.bitsquare.gui.util.BSFormatter;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.util.StringConverter;
@ -44,15 +43,15 @@ public class TooltipContent extends GridPane {
private Label closeValue = new Label();
private Label highValue = new Label();
private Label lowValue = new Label();
private BSFormatter formatter;
private StringConverter<Number> toolTipStringConverter;
private StringConverter<Number> priceStringConverter;
TooltipContent(StringConverter<Number> toolTipStringConverter) {
this.toolTipStringConverter = toolTipStringConverter;
TooltipContent(StringConverter<Number> priceStringConverter) {
this.priceStringConverter = priceStringConverter;
Label open = new Label("Open:");
Label close = new Label("Close:");
Label high = new Label("High:");
Label low = new Label("Low:");
Label volume = new Label("Volume:");
/* open.getStyleClass().add("candlestick-tooltip-label");
close.getStyleClass().add("candlestick-tooltip-label");
high.getStyleClass().add("candlestick-tooltip-label");
@ -69,11 +68,12 @@ public class TooltipContent extends GridPane {
}
public void update(double open, double close, double high, double low) {
if (toolTipStringConverter != null) {
openValue.setText(toolTipStringConverter.toString(open));
closeValue.setText(toolTipStringConverter.toString(close));
highValue.setText(toolTipStringConverter.toString(high));
lowValue.setText(toolTipStringConverter.toString(low));
if (priceStringConverter != null) {
openValue.setText(priceStringConverter.toString(open));
closeValue.setText(priceStringConverter.toString(close));
highValue.setText(priceStringConverter.toString(high));
lowValue.setText(priceStringConverter.toString(low));
}
}
}

View file

@ -32,6 +32,7 @@
package io.bitsquare.gui.main.markets.trades.candlestick;
import javafx.scene.Group;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.Region;
import javafx.util.StringConverter;
import org.slf4j.Logger;
@ -43,17 +44,23 @@ import org.slf4j.LoggerFactory;
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;
private final StringConverter<Number> volumeStringConverter;
VolumeBar(String seriesStyleClass, String dataStyleClass, StringConverter<Number> toolTipStringConverter) {
setAutoSizeChildren(false);
bar.setOpacity(0.7);
getChildren().add(bar);
private final Region bar = new Region();
private final Tooltip tooltip;
VolumeBar(String seriesStyleClass, String dataStyleClass, StringConverter<Number> volumeStringConverter) {
this.seriesStyleClass = seriesStyleClass;
this.dataStyleClass = dataStyleClass;
this.volumeStringConverter = volumeStringConverter;
setAutoSizeChildren(false);
getChildren().add(bar);
updateStyleClasses();
tooltip = new Tooltip();
Tooltip.install(this, tooltip);
}
public void setSeriesAndDataStyleClasses(String seriesStyleClass, String dataStyleClass) {
@ -62,13 +69,9 @@ public class VolumeBar extends Group {
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));
public void update(double height, double candleWidth, double volume) {
bar.resizeRelocate(-candleWidth / 2, 0, candleWidth, height);
tooltip.setText("Accumulated volume: " + volumeStringConverter.toString(volume));
}
private void updateStyleClasses() {

View file

@ -94,18 +94,23 @@ public class VolumeChart extends XYChart<Number, Number> {
double x = getXAxis().getDisplayPosition(getCurrentDisplayedXValue(item));
double y = getYAxis().getDisplayPosition(getCurrentDisplayedYValue(item));
Node itemNode = item.getNode();
if (itemNode instanceof VolumeBar) {
CandleStickExtraValues extra = (CandleStickExtraValues) item.getExtraValue();
if (itemNode instanceof VolumeBar && extra != 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
}
volumeBar.update(y, candleWidth);
// position the volumeBar
// 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, extra.getVolume());
volumeBar.setLayoutX(x);
volumeBar.setLayoutY(y);
volumeBar.setLayoutY(upperYPos);
}
}
}
@ -221,11 +226,10 @@ public class VolumeChart extends XYChart<Number, Number> {
List<Number> xData = null;
List<Number> yData = null;
if (xa.isAutoRanging()) {
xData = new ArrayList<Number>();
}
if (ya.isAutoRanging()) {
yData = new ArrayList<Number>();
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()) {