mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-23 23:06:39 +01:00
Improve readability of the daily burnt BSQ chart
Relevant issue thread: #3753 Currently the daily burnt BSQ chart under 'DAO -> Facts and Figures' is distorted by outliers. This introduces a 'Zoom to inliers' toggle (off by default), which, when toggled on, effectively zooms the chart to inliers, thus removing the distortion. Also, a moving average is plotted, to further improve the chart's readibility. The chart is also changed from an area chart to a line chart, on the presumption that it was an area chart for cosmetic reasons, but now that there are two series in it (the moving average was added) an area chart makes less sense. Another noteworthy change is that the other chart in the screen, monthly issued BSQ, has its Y axis set to start at zero, so as to improve readability. This might seem outside the scope of this commit, but the other changes involved some refactoring, which involved cleaning up some duplicated logic, which involved configuring both of these charts together, which involved forcing zero to be on the axis. This implementation mixes some plotting logic (responsible for zooming in on inliers) into the view logic, because I opted to implement said zooming as an external manipulation of a chart's axis. I chose this in favor of implementing a new Chart, because it would have required including multiple large classes (relevant JavaFX's classes can't be ergonomically extended) to the code base. I presumed that my chosen solution will be easier to maintain. I am not entirely happy with this choice and can see myself introducing some plotting-related classes to encapsulate creating charts like these, thus unmixing plotting logic from view logic. In the meantime this is a working solution, and I plan to continue working on these charts in the near future.
This commit is contained in:
parent
c6941cf412
commit
86489e0d74
5 changed files with 309 additions and 51 deletions
|
@ -2251,6 +2251,8 @@ dao.factsAndFigures.supply.compRequestIssueAmount=BSQ issued for compensation re
|
|||
dao.factsAndFigures.supply.reimbursementAmount=BSQ issued for reimbursement requests
|
||||
|
||||
dao.factsAndFigures.supply.burnt=BSQ burnt
|
||||
dao.factsAndFigures.supply.burntMovingAverage=15 days moving average
|
||||
dao.factsAndFigures.supply.burntZoomToInliers=Zoom to inliers
|
||||
|
||||
dao.factsAndFigures.supply.locked=Global state of locked BSQ
|
||||
dao.factsAndFigures.supply.totalLockedUpAmount=Locked up in bonds
|
||||
|
|
|
@ -1621,6 +1621,17 @@ textfield */
|
|||
-fx-stroke: -bs-buy;
|
||||
}
|
||||
|
||||
/* The .chart-line-symbol rules change the color of the legend symbol */
|
||||
#charts-dao .default-color0.chart-series-line { -fx-stroke: -bs-chart-dao-line1; }
|
||||
#charts-dao .default-color0.chart-line-symbol { -fx-background-color: -bs-chart-dao-line1, -bs-background-color; }
|
||||
|
||||
#charts-dao .default-color1.chart-series-line { -fx-stroke: -bs-chart-dao-line2; }
|
||||
#charts-dao .default-color1.chart-line-symbol { -fx-background-color: -bs-chart-dao-line2, -bs-background-color; }
|
||||
|
||||
#charts-dao .chart-series-line {
|
||||
-fx-stroke-width: 1px;
|
||||
}
|
||||
|
||||
#charts .default-color0.chart-series-area-fill {
|
||||
-fx-fill: -bs-sell-transparent;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,9 @@ package bisq.desktop.main.dao.economy.supply;
|
|||
import bisq.desktop.common.view.ActivatableView;
|
||||
import bisq.desktop.common.view.FxmlView;
|
||||
import bisq.desktop.components.TitledGroupBg;
|
||||
import bisq.desktop.util.AxisInlierUtils;
|
||||
import bisq.desktop.util.Layout;
|
||||
import bisq.desktop.util.MovingAverageUtils;
|
||||
|
||||
import bisq.core.dao.DaoFacade;
|
||||
import bisq.core.dao.state.DaoStateListener;
|
||||
|
@ -39,18 +41,24 @@ import org.bitcoinj.core.Coin;
|
|||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.chart.AreaChart;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Side;
|
||||
|
||||
import javafx.collections.ListChangeListener;
|
||||
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
@ -68,9 +76,15 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Spliterators.AbstractSpliterator;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.addSlideToggleButton;
|
||||
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
|
||||
import static bisq.desktop.util.FormBuilder.addTopLabelReadOnlyTextField;
|
||||
|
||||
|
@ -92,7 +106,17 @@ public class SupplyView extends ActivatableView<GridPane, Void> implements DaoSt
|
|||
private TextField genesisIssueAmountTextField, compRequestIssueAmountTextField, reimbursementAmountTextField,
|
||||
totalBurntFeeAmountTextField, totalLockedUpAmountTextField, totalUnlockingAmountTextField,
|
||||
totalUnlockedAmountTextField, totalConfiscatedAmountTextField, totalAmountOfInvalidatedBsqTextField;
|
||||
private XYChart.Series<Number, Number> seriesBSQIssued, seriesBSQBurnt;
|
||||
private XYChart.Series<Number, Number> seriesBSQIssued, seriesBSQBurnt, seriesBSQBurntMA;
|
||||
private ListChangeListener changeListenerBSQBurnt;
|
||||
private NumberAxis yAxisBSQBurnt;
|
||||
|
||||
private ToggleButton zoomToInliersSlide;
|
||||
private boolean isZoomingToInliers = false;
|
||||
|
||||
// Parameters for zooming to inliers; explanations in AxisInlierUtils.
|
||||
private int chartMaxNumberOfTicks = 10;
|
||||
private double chartPercentToTrim = 5;
|
||||
private double chartHowManyStdDevsConstituteOutlier = 10;
|
||||
|
||||
private static final Map<String, TemporalAdjuster> ADJUSTERS = new HashMap<>();
|
||||
|
||||
|
@ -112,12 +136,12 @@ public class SupplyView extends ActivatableView<GridPane, Void> implements DaoSt
|
|||
|
||||
@Override
|
||||
public void initialize() {
|
||||
|
||||
ADJUSTERS.put(MONTH, TemporalAdjusters.firstDayOfMonth());
|
||||
ADJUSTERS.put(DAY, TemporalAdjusters.ofDateAdjuster(d -> d));
|
||||
|
||||
createSupplyIncreasedInformation();
|
||||
createSupplyReducedInformation();
|
||||
|
||||
createSupplyLockedInformation();
|
||||
}
|
||||
|
||||
|
@ -125,12 +149,22 @@ public class SupplyView extends ActivatableView<GridPane, Void> implements DaoSt
|
|||
protected void activate() {
|
||||
daoFacade.addBsqStateListener(this);
|
||||
|
||||
if (isZoomingToInliers) {
|
||||
activateZoomingToInliers();
|
||||
}
|
||||
|
||||
updateWithBsqBlockChainData();
|
||||
|
||||
activateButtons();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
daoFacade.removeBsqStateListener(this);
|
||||
|
||||
deactivateZoomingToInliers();
|
||||
|
||||
deactivateButtons();
|
||||
}
|
||||
|
||||
|
||||
|
@ -163,7 +197,11 @@ public class SupplyView extends ActivatableView<GridPane, Void> implements DaoSt
|
|||
|
||||
|
||||
seriesBSQIssued = new XYChart.Series<>();
|
||||
createChart(seriesBSQIssued, Res.get("dao.factsAndFigures.supply.issued"), "MMM uu");
|
||||
|
||||
var chart = createBSQIssuedChart(seriesBSQIssued);
|
||||
|
||||
var chartPane = wrapInChartPane(chart);
|
||||
root.getChildren().add(chartPane);
|
||||
}
|
||||
|
||||
private void createSupplyReducedInformation() {
|
||||
|
@ -174,8 +212,16 @@ public class SupplyView extends ActivatableView<GridPane, Void> implements DaoSt
|
|||
totalAmountOfInvalidatedBsqTextField = addTopLabelReadOnlyTextField(root, gridRow, 1,
|
||||
Res.get("dao.factsAndFigures.supply.invalidTxs"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
|
||||
|
||||
var buttonTitle = Res.get("dao.factsAndFigures.supply.burntZoomToInliers");
|
||||
zoomToInliersSlide = addSlideToggleButton(root, ++gridRow, buttonTitle);
|
||||
|
||||
seriesBSQBurnt = new XYChart.Series<>();
|
||||
createChart(seriesBSQBurnt, Res.get("dao.factsAndFigures.supply.burnt"), "d MMM");
|
||||
seriesBSQBurntMA = new XYChart.Series<>();
|
||||
|
||||
var chart = createBSQBurntChart(seriesBSQBurnt, seriesBSQBurntMA);
|
||||
|
||||
var chartPane = wrapInChartPane(chart);
|
||||
root.getChildren().add(chartPane);
|
||||
}
|
||||
|
||||
private void createSupplyLockedInformation() {
|
||||
|
@ -195,14 +241,87 @@ public class SupplyView extends ActivatableView<GridPane, Void> implements DaoSt
|
|||
Res.get("dao.factsAndFigures.supply.totalConfiscatedAmount")).second;
|
||||
}
|
||||
|
||||
private void createChart(XYChart.Series<Number, Number> series, String seriesLabel, String datePattern) {
|
||||
private Node createBSQIssuedChart(XYChart.Series<Number, Number> series) {
|
||||
NumberAxis xAxis = new NumberAxis();
|
||||
xAxis.setForceZeroInRange(false);
|
||||
xAxis.setAutoRanging(true);
|
||||
xAxis.setTickLabelGap(6);
|
||||
xAxis.setTickMarkVisible(false);
|
||||
xAxis.setMinorTickVisible(false);
|
||||
xAxis.setTickLabelFormatter(new StringConverter<>() {
|
||||
configureAxis(xAxis);
|
||||
xAxis.setTickLabelFormatter(getTimestampTickLabelFormatter("MMM uu"));
|
||||
|
||||
NumberAxis yAxis = new NumberAxis();
|
||||
configureYAxis(yAxis);
|
||||
yAxis.setTickLabelFormatter(BSQPriceTickLabelFormatter);
|
||||
|
||||
AreaChart<Number, Number> chart = new AreaChart<>(xAxis, yAxis);
|
||||
configureChart(chart);
|
||||
|
||||
series.setName(Res.get("dao.factsAndFigures.supply.issued"));
|
||||
chart.getData().add(series);
|
||||
|
||||
return chart;
|
||||
}
|
||||
|
||||
private Node createBSQBurntChart(
|
||||
XYChart.Series<Number, Number> seriesBSQBurnt,
|
||||
XYChart.Series<Number, Number> seriesBSQBurntMA
|
||||
) {
|
||||
Supplier<NumberAxis> makeXAxis = () -> {
|
||||
NumberAxis xAxis = new NumberAxis();
|
||||
configureAxis(xAxis);
|
||||
xAxis.setTickLabelFormatter(getTimestampTickLabelFormatter("d MMM"));
|
||||
return xAxis;
|
||||
};
|
||||
|
||||
Supplier<NumberAxis> makeYAxis = () -> {
|
||||
NumberAxis yAxis = new NumberAxis();
|
||||
configureYAxis(yAxis);
|
||||
yAxis.setTickLabelFormatter(BSQPriceTickLabelFormatter);
|
||||
return yAxis;
|
||||
};
|
||||
|
||||
seriesBSQBurnt.setName(Res.get("dao.factsAndFigures.supply.burnt"));
|
||||
|
||||
var burntMALabel = Res.get("dao.factsAndFigures.supply.burntMovingAverage");
|
||||
seriesBSQBurntMA.setName(burntMALabel);
|
||||
|
||||
var yAxis = makeYAxis.get();
|
||||
initializeChangeListener(yAxis);
|
||||
|
||||
var chart = new LineChart<>(makeXAxis.get(), yAxis);
|
||||
|
||||
chart.getData().addAll(seriesBSQBurnt, seriesBSQBurntMA);
|
||||
|
||||
configureChart(chart);
|
||||
chart.setCreateSymbols(false);
|
||||
chart.setLegendVisible(true);
|
||||
|
||||
return chart;
|
||||
}
|
||||
|
||||
private void initializeChangeListener(NumberAxis axis) {
|
||||
// Keep a class-scope reference. Needed for switching between inliers-only and full chart.
|
||||
yAxisBSQBurnt = axis;
|
||||
|
||||
changeListenerBSQBurnt = AxisInlierUtils.getListenerThatZoomsToInliers(
|
||||
yAxisBSQBurnt, chartMaxNumberOfTicks, chartPercentToTrim, chartHowManyStdDevsConstituteOutlier);
|
||||
}
|
||||
|
||||
private void configureYAxis(NumberAxis axis) {
|
||||
configureAxis(axis);
|
||||
|
||||
axis.setForceZeroInRange(true);
|
||||
axis.setTickLabelGap(5);
|
||||
axis.setSide(Side.RIGHT);
|
||||
}
|
||||
|
||||
private void configureAxis(NumberAxis axis) {
|
||||
axis.setForceZeroInRange(false);
|
||||
axis.setAutoRanging(true);
|
||||
axis.setTickMarkVisible(false);
|
||||
axis.setMinorTickVisible(false);
|
||||
axis.setTickLabelGap(6);
|
||||
}
|
||||
|
||||
private StringConverter<Number> getTimestampTickLabelFormatter(String datePattern) {
|
||||
return new StringConverter<Number>() {
|
||||
@Override
|
||||
public String toString(Number timestamp) {
|
||||
LocalDateTime localDateTime = LocalDateTime.ofEpochSecond(timestamp.longValue(),
|
||||
|
@ -214,54 +333,48 @@ public class SupplyView extends ActivatableView<GridPane, Void> implements DaoSt
|
|||
public Number fromString(String string) {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
NumberAxis yAxis = new NumberAxis();
|
||||
yAxis.setForceZeroInRange(false);
|
||||
yAxis.setSide(Side.RIGHT);
|
||||
yAxis.setAutoRanging(true);
|
||||
yAxis.setTickMarkVisible(false);
|
||||
yAxis.setMinorTickVisible(false);
|
||||
yAxis.setTickLabelGap(5);
|
||||
yAxis.setTickLabelFormatter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(Number marketPrice) {
|
||||
return bsqFormatter.formatBSQSatoshisWithCode(marketPrice.longValue());
|
||||
}
|
||||
private StringConverter<Number> BSQPriceTickLabelFormatter =
|
||||
new StringConverter<Number>() {
|
||||
@Override
|
||||
public String toString(Number marketPrice) {
|
||||
return bsqFormatter.formatBSQSatoshisWithCode(marketPrice.longValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number fromString(String string) {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public Number fromString(String string) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
series.setName(seriesLabel);
|
||||
|
||||
AreaChart<Number, Number> chart = new AreaChart<>(xAxis, yAxis);
|
||||
private <X, Y> void configureChart(XYChart<X, Y> chart) {
|
||||
chart.setLegendVisible(false);
|
||||
chart.setAnimated(false);
|
||||
chart.setId("charts-dao");
|
||||
chart.setMinHeight(250);
|
||||
chart.setPrefHeight(250);
|
||||
chart.setCreateSymbols(true);
|
||||
chart.setPadding(new Insets(0));
|
||||
chart.getData().add(series);
|
||||
|
||||
chart.setMinHeight(300);
|
||||
chart.setPrefHeight(300);
|
||||
chart.setPadding(new Insets(0));
|
||||
}
|
||||
|
||||
private Pane wrapInChartPane(Node child) {
|
||||
AnchorPane chartPane = new AnchorPane();
|
||||
chartPane.getStyleClass().add("chart-pane");
|
||||
|
||||
AnchorPane.setTopAnchor(chart, 15d);
|
||||
AnchorPane.setBottomAnchor(chart, 10d);
|
||||
AnchorPane.setLeftAnchor(chart, 25d);
|
||||
AnchorPane.setRightAnchor(chart, 10d);
|
||||
AnchorPane.setTopAnchor(child, 15d);
|
||||
AnchorPane.setBottomAnchor(child, 10d);
|
||||
AnchorPane.setLeftAnchor(child, 25d);
|
||||
AnchorPane.setRightAnchor(child, 10d);
|
||||
|
||||
chartPane.getChildren().add(chart);
|
||||
chartPane.getChildren().add(child);
|
||||
|
||||
GridPane.setColumnSpan(chartPane, 2);
|
||||
GridPane.setRowIndex(chartPane, ++gridRow);
|
||||
GridPane.setMargin(chartPane, new Insets(10, 0, 0, 0));
|
||||
|
||||
root.getChildren().add(chartPane);
|
||||
return chartPane;
|
||||
}
|
||||
|
||||
private void updateWithBsqBlockChainData() {
|
||||
|
@ -288,11 +401,16 @@ public class SupplyView extends ActivatableView<GridPane, Void> implements DaoSt
|
|||
String minusSign = totalAmountOfInvalidatedBsq.isPositive() ? "-" : "";
|
||||
totalAmountOfInvalidatedBsqTextField.setText(minusSign + bsqFormatter.formatAmountWithGroupSeparatorAndCode(totalAmountOfInvalidatedBsq));
|
||||
|
||||
updateCharts();
|
||||
updateChartSeries();
|
||||
}
|
||||
|
||||
private void updateCharts() {
|
||||
seriesBSQIssued.getData().clear();
|
||||
private void updateChartSeries() {
|
||||
var updatedBurntBsq = updateBSQBurnt();
|
||||
updateBSQBurntMA(updatedBurntBsq);
|
||||
updateBSQIssued();
|
||||
}
|
||||
|
||||
private List<XYChart.Data<Number, Number>> updateBSQBurnt() {
|
||||
seriesBSQBurnt.getData().clear();
|
||||
|
||||
Set<Tx> burntTxs = new HashSet<>(daoStateService.getBurntFeeTxs());
|
||||
|
@ -306,16 +424,61 @@ public class SupplyView extends ActivatableView<GridPane, Void> implements DaoSt
|
|||
List<XYChart.Data<Number, Number>> updatedBurntBsq = burntBsqByDay.keySet().stream()
|
||||
.map(date -> {
|
||||
ZonedDateTime zonedDateTime = date.atStartOfDay(ZoneId.systemDefault());
|
||||
return new XYChart.Data<Number, Number>(zonedDateTime.toInstant().getEpochSecond(), burntBsqByDay.get(date)
|
||||
.stream()
|
||||
.mapToDouble(Tx::getBurntBsq)
|
||||
.sum()
|
||||
return new XYChart.Data<Number, Number>(
|
||||
zonedDateTime.toInstant().getEpochSecond(),
|
||||
burntBsqByDay.get(date)
|
||||
.stream()
|
||||
.mapToDouble(Tx::getBurntBsq)
|
||||
.sum()
|
||||
);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
seriesBSQBurnt.getData().setAll(updatedBurntBsq);
|
||||
|
||||
return updatedBurntBsq;
|
||||
}
|
||||
|
||||
private void updateBSQBurntMA(List<XYChart.Data<Number, Number>> updatedBurntBsq) {
|
||||
seriesBSQBurntMA.getData().clear();
|
||||
|
||||
Comparator<Number> compareXChronology =
|
||||
(x1, x2) -> x1.intValue() - x2.intValue();
|
||||
|
||||
Comparator<XYChart.Data<Number, Number>> compareXyDataChronology =
|
||||
(xyData1, xyData2) ->
|
||||
compareXChronology.compare(
|
||||
xyData1.getXValue(),
|
||||
xyData2.getXValue());
|
||||
|
||||
var sortedUpdatedBurntBsq = updatedBurntBsq
|
||||
.stream()
|
||||
.sorted(compareXyDataChronology)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
var burntBsqXValues = sortedUpdatedBurntBsq.stream().map(xyData -> xyData.getXValue());
|
||||
var burntBsqYValues = sortedUpdatedBurntBsq.stream().map(xyData -> xyData.getYValue());
|
||||
|
||||
var maPeriod = 15;
|
||||
var burntBsqMAYValues =
|
||||
MovingAverageUtils.simpleMovingAverage(
|
||||
burntBsqYValues,
|
||||
maPeriod);
|
||||
|
||||
BiFunction<Number, Double, XYChart.Data<Number, Number>> xyToXyData =
|
||||
(xValue, yValue) -> new XYChart.Data<Number, Number>(xValue, yValue);
|
||||
|
||||
List<XYChart.Data<Number, Number>> burntBsqMA =
|
||||
zip(burntBsqXValues, burntBsqMAYValues, xyToXyData)
|
||||
.filter(xyData -> Double.isFinite(xyData.getYValue().doubleValue()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
seriesBSQBurntMA.getData().setAll(burntBsqMA);
|
||||
}
|
||||
|
||||
private void updateBSQIssued() {
|
||||
seriesBSQIssued.getData().clear();
|
||||
|
||||
Stream<Issuance> bsqByCompensation = daoStateService.getIssuanceSet(IssuanceType.COMPENSATION).stream()
|
||||
.sorted(Comparator.comparing(Issuance::getChainHeight));
|
||||
|
||||
|
@ -338,5 +501,79 @@ public class SupplyView extends ActivatableView<GridPane, Void> implements DaoSt
|
|||
|
||||
seriesBSQIssued.getData().setAll(updatedAddedBSQ);
|
||||
}
|
||||
}
|
||||
|
||||
private void activateButtons() {
|
||||
zoomToInliersSlide.setSelected(isZoomingToInliers);
|
||||
zoomToInliersSlide.setOnAction(e -> handleZoomToInliersSlide(!isZoomingToInliers));
|
||||
}
|
||||
|
||||
private void deactivateButtons() {
|
||||
zoomToInliersSlide.setOnAction(null);
|
||||
}
|
||||
|
||||
private void handleZoomToInliersSlide(boolean shouldActivate) {
|
||||
isZoomingToInliers = !isZoomingToInliers;
|
||||
if (shouldActivate) {
|
||||
activateZoomingToInliers();
|
||||
} else {
|
||||
deactivateZoomingToInliers();
|
||||
}
|
||||
}
|
||||
|
||||
private void activateZoomingToInliers() {
|
||||
seriesBSQBurnt.getData().addListener(changeListenerBSQBurnt);
|
||||
|
||||
// Initial zoom has to be triggered manually; otherwise, it
|
||||
// would be triggered only on a change event in the series
|
||||
triggerZoomToInliers();
|
||||
}
|
||||
|
||||
private void deactivateZoomingToInliers() {
|
||||
seriesBSQBurnt.getData().removeListener(changeListenerBSQBurnt);
|
||||
|
||||
// Reactivate automatic ranging
|
||||
yAxisBSQBurnt.autoRangingProperty().set(true);
|
||||
}
|
||||
|
||||
private void triggerZoomToInliers() {
|
||||
var xyValues = seriesBSQBurnt.getData();
|
||||
AxisInlierUtils.zoomToInliers(
|
||||
yAxisBSQBurnt,
|
||||
xyValues,
|
||||
chartMaxNumberOfTicks,
|
||||
chartPercentToTrim,
|
||||
chartHowManyStdDevsConstituteOutlier
|
||||
);
|
||||
}
|
||||
|
||||
// When Guava version is bumped to at least 21.0,
|
||||
// can be replaced with com.google.common.collect.Streams.zip
|
||||
public static <L, R, T> Stream<T> zip(
|
||||
Stream<L> leftStream,
|
||||
Stream<R> rightStream,
|
||||
BiFunction<L, R, T> combiner
|
||||
) {
|
||||
var lefts = leftStream.spliterator();
|
||||
var rights = rightStream.spliterator();
|
||||
var spliterator =
|
||||
new AbstractSpliterator<T>(
|
||||
Long.min(
|
||||
lefts.estimateSize(),
|
||||
rights.estimateSize()
|
||||
),
|
||||
lefts.characteristics() & rights.characteristics()
|
||||
) {
|
||||
@Override
|
||||
public boolean tryAdvance(Consumer<? super T> action) {
|
||||
return lefts.tryAdvance(
|
||||
left -> rights.tryAdvance(
|
||||
right -> action.accept(combiner.apply(left, right))
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
var isParallel = false;
|
||||
var stream = StreamSupport.stream(spliterator, isParallel);
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,6 +128,10 @@
|
|||
-bs-chart-tick: rgba(255, 255, 255, 0.7);
|
||||
-bs-chart-lines: rgba(0, 0, 0, 0.3);
|
||||
-bs-white: white;
|
||||
|
||||
/* dao chart colors */
|
||||
-bs-chart-dao-line1: -bs-color-green-5;
|
||||
-bs-chart-dao-line2: -bs-color-blue-2;
|
||||
}
|
||||
|
||||
/* list view */
|
||||
|
|
|
@ -101,4 +101,8 @@
|
|||
-fx-default-button: derive(-fx-accent, 95%);
|
||||
-bs-progress-bar-track: #e0e0e0;
|
||||
-bs-white: white;
|
||||
|
||||
/* dao chart colors */
|
||||
-bs-chart-dao-line1: -bs-color-green-3;
|
||||
-bs-chart-dao-line2: -bs-color-blue-5;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue