diff --git a/core/src/main/java/bisq/core/dao/state/DaoStateService.java b/core/src/main/java/bisq/core/dao/state/DaoStateService.java index 1216b64524..2cb1d29206 100644 --- a/core/src/main/java/bisq/core/dao/state/DaoStateService.java +++ b/core/src/main/java/bisq/core/dao/state/DaoStateService.java @@ -30,7 +30,6 @@ import bisq.core.dao.state.model.blockchain.TxOutput; import bisq.core.dao.state.model.blockchain.TxOutputKey; import bisq.core.dao.state.model.blockchain.TxOutputType; import bisq.core.dao.state.model.blockchain.TxType; -import bisq.core.dao.state.model.governance.BsqSupplyChange; import bisq.core.dao.state.model.governance.Cycle; import bisq.core.dao.state.model.governance.DecryptedBallotsWithMerits; import bisq.core.dao.state.model.governance.EvaluatedProposal; @@ -1050,18 +1049,6 @@ public class DaoStateService implements DaoSetupService { return getTxOutputsByTxOutputType(TxOutputType.PROOF_OF_BURN_OP_RETURN_OUTPUT); } - public Stream getBsqSupplyChanges() { - Stream issued = getIssuanceItems() - .stream() - .map(issuance -> new BsqSupplyChange(getBlockTime(issuance.getChainHeight()), issuance.getAmount())); - - Stream burned = getUnorderedTxStream() - .filter(tx -> tx.getTxType() == TxType.PROOF_OF_BURN || tx.getTxType() == TxType.PAY_TRADE_FEE) - .map(tx -> new BsqSupplyChange(tx.getTime(), -tx.getBurntBsq())); - - return Stream.concat(issued, burned); - } - /////////////////////////////////////////////////////////////////////////////////////////// // Listeners diff --git a/core/src/main/java/bisq/core/dao/state/model/governance/BsqSupplyChange.java b/core/src/main/java/bisq/core/dao/state/model/governance/BsqSupplyChange.java deleted file mode 100644 index a37950f100..0000000000 --- a/core/src/main/java/bisq/core/dao/state/model/governance/BsqSupplyChange.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq 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. - * - * Bisq 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 Bisq. If not, see . - */ - -package bisq.core.dao.state.model.governance; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@AllArgsConstructor -public class BsqSupplyChange { - @Getter - long time; - - @Getter - long value; -} diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 45d6dccd92..233f7c96b4 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -2624,7 +2624,10 @@ dao.factsAndFigures.supply.totalBurned=Total burned BSQ dao.factsAndFigures.supply.totalBurned.tooltip=Total burned BSQ is the sum of BSQ trade fees and all 'Proof of Burn' transactions.\n\ 'Proof of Burn' transactions started in Dec 2019. dao.factsAndFigures.supply.totalSupply=Total BSQ supply -dao.factsAndFigures.supply.totalSupply.tooltip=Total BSQ supply is the sum of all issues BSQ on top of the Genesis issuance and the total burned BSQ. +dao.factsAndFigures.supply.totalSupply.tooltip=Total BSQ supply is 'Change of BSQ supply' + genesis issuance +dao.factsAndFigures.supply.supplyChange=Change of BSQ supply +dao.factsAndFigures.supply.supplyChange.tooltip=Change of BSQ supply reflects the change of all issued BSQ (compensation requests + reimbursement requests)\n\ + minus all burned BSQ (BSQ trade fees, Burned BSQ from BTC trade fees, burned BSQ from arbitration cases). dao.factsAndFigures.supply.chart.tradeFee.toolTip={0}\n{1} dao.factsAndFigures.supply.burnt=BSQ burnt diff --git a/desktop/src/main/java/bisq/desktop/bisq.css b/desktop/src/main/java/bisq/desktop/bisq.css index 69eb7b2d5f..cb08a22eec 100644 --- a/desktop/src/main/java/bisq/desktop/bisq.css +++ b/desktop/src/main/java/bisq/desktop/bisq.css @@ -1877,6 +1877,16 @@ textfield */ -fx-background-color: -bs-chart-dao-line13, -bs-chart-dao-line13; } + +#charts-dao .default-color13.chart-series-line { + -fx-stroke: -bs-chart-dao-line14; +} + +#charts-dao .default-color13.chart-line-symbol { + -fx-background-color: -bs-chart-dao-line14, -bs-chart-dao-line14; +} + + #charts-legend-toggle0 { -jfx-toggle-color: -bs-chart-dao-line1 } @@ -1929,6 +1939,10 @@ textfield */ -jfx-toggle-color: -bs-chart-dao-line13; } +#charts-legend-toggle13 { + -jfx-toggle-color: -bs-chart-dao-line14; +} + #charts-dao .chart-series-line { -fx-stroke-width: 2px; } diff --git a/desktop/src/main/java/bisq/desktop/components/chart/ChartView.java b/desktop/src/main/java/bisq/desktop/components/chart/ChartView.java index e960fd2713..6e2ccbe6a9 100644 --- a/desktop/src/main/java/bisq/desktop/components/chart/ChartView.java +++ b/desktop/src/main/java/bisq/desktop/components/chart/ChartView.java @@ -400,7 +400,7 @@ public abstract class ChartView { AutoTooltipSlideToggleButton toggle = new AutoTooltipSlideToggleButton(); - toggle.setMinWidth(220); + toggle.setMinWidth(300); toggle.setAlignment(Pos.TOP_LEFT); String seriesId = getSeriesId(series); legendToggleBySeriesName.put(seriesId, toggle); diff --git a/desktop/src/main/java/bisq/desktop/components/chart/TemporalAdjusterModel.java b/desktop/src/main/java/bisq/desktop/components/chart/TemporalAdjusterModel.java index c3d3d92567..d8cf5c3bb8 100644 --- a/desktop/src/main/java/bisq/desktop/components/chart/TemporalAdjusterModel.java +++ b/desktop/src/main/java/bisq/desktop/components/chart/TemporalAdjusterModel.java @@ -81,7 +81,7 @@ public class TemporalAdjusterModel { } } - protected TemporalAdjuster temporalAdjuster = Interval.DAY.getAdjuster(); + protected TemporalAdjuster temporalAdjuster = Interval.MONTH.getAdjuster(); public void setTemporalAdjuster(TemporalAdjuster temporalAdjuster) { this.temporalAdjuster = temporalAdjuster; diff --git a/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/dao/DaoChartDataModel.java b/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/dao/DaoChartDataModel.java index 0aca372410..3650e5a35f 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/dao/DaoChartDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/dao/DaoChartDataModel.java @@ -21,7 +21,6 @@ import bisq.desktop.components.chart.ChartDataModel; import bisq.core.dao.state.DaoStateService; import bisq.core.dao.state.model.blockchain.Tx; -import bisq.core.dao.state.model.governance.BsqSupplyChange; import bisq.core.dao.state.model.governance.Issuance; import bisq.core.dao.state.model.governance.IssuanceType; @@ -33,18 +32,15 @@ import javax.inject.Singleton; import java.time.Instant; import java.util.Collection; -import java.util.Comparator; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; @@ -56,7 +52,7 @@ public class DaoChartDataModel extends ChartDataModel { private final DaoStateService daoStateService; private final Function blockTimeOfIssuanceFunction; - private Map totalSupplyByInterval, totalIssuedByInterval, compensationByInterval, + private Map totalSupplyByInterval, supplyChangeByInterval, totalIssuedByInterval, compensationByInterval, reimbursementByInterval, reimbursementByIntervalAfterTagging, totalBurnedByInterval, bsqTradeFeeByInterval, bsqTradeFeeByIntervalAfterTagging, proofOfBurnByInterval, proofOfBurnFromBtcFeesByInterval, proofOfBurnFromArbitrationByInterval, @@ -86,6 +82,7 @@ public class DaoChartDataModel extends ChartDataModel { @Override protected void invalidateCache() { totalSupplyByInterval = null; + supplyChangeByInterval = null; totalIssuedByInterval = null; compensationByInterval = null; reimbursementByInterval = null; @@ -134,18 +131,6 @@ public class DaoChartDataModel extends ChartDataModel { // Data for chart /////////////////////////////////////////////////////////////////////////////////////////// - Map getTotalSupplyByInterval() { - if (totalSupplyByInterval != null) { - return totalSupplyByInterval; - } - - totalSupplyByInterval = getTotalSupplyByInterval( - daoStateService.getBsqSupplyChanges(), - getDateFilter() - ); - return totalSupplyByInterval; - } - Map getArbitrationDiffByInterval() { if (arbitrationDiffByInterval != null) { return arbitrationDiffByInterval; @@ -294,33 +279,32 @@ public class DaoChartDataModel extends ChartDataModel { return proofOfBurnFromArbitrationByInterval; } + Map getTotalSupplyByInterval() { + if (totalSupplyByInterval != null) { + return totalSupplyByInterval; + } + + long genesisValue = daoStateService.getGenesisTotalSupply().value; + totalSupplyByInterval = getSupplyChangeByInterval().entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> genesisValue + e.getValue())); + return totalSupplyByInterval; + } + + Map getSupplyChangeByInterval() { + if (supplyChangeByInterval != null) { + return supplyChangeByInterval; + } + + Map issued = getTotalIssuedByInterval(); + Map burned = getTotalBurnedByInterval(); + supplyChangeByInterval = getMergedMap(issued, burned, (a, b) -> a - b); + return supplyChangeByInterval; + } /////////////////////////////////////////////////////////////////////////////////////////// // Aggregated collection data by interval /////////////////////////////////////////////////////////////////////////////////////////// - private Map getTotalSupplyByInterval(Stream bsqSupplyChanges, - Predicate dateFilter) { - AtomicLong supply = new AtomicLong(DaoEconomyHistoricalData.TOTAL_SUPPLY_BY_CYCLE_DATE.get(1555340856L)); - - return bsqSupplyChanges - .collect(Collectors.groupingBy(tx -> toTimeInterval(Instant.ofEpochMilli(tx.getTime())))) - .entrySet() - .stream() - .sorted(Comparator.comparingLong(Map.Entry::getKey)) - .map(e -> new BsqSupplyChange( - e.getKey(), - supply.addAndGet(e - .getValue() - .stream() - .mapToLong(BsqSupplyChange::getValue) - .sum() - )) - ) - .filter(t -> dateFilter.test(t.getTime())) - .collect(Collectors.toMap(BsqSupplyChange::getTime, BsqSupplyChange::getValue)); - } - private Map getIssuedBsqByInterval(Set issuanceSet, Predicate dateFilter) { return issuanceSet.stream() .collect(Collectors.groupingBy(issuance -> @@ -377,14 +361,13 @@ public class DaoChartDataModel extends ChartDataModel { /////////////////////////////////////////////////////////////////////////////////////////// // We did not use the reimbursement requests initially (but the compensation requests) because the limits - // have been too low. Over time it got mixed in compensation requests and reimbursement requests. - // To reflect that we use static data derived from the Github data. For new data we do not need that anymore + // have been too low. Over time, it got mixed in compensation requests and reimbursement requests. + // To reflect that we use static data derived from the GitHub data. For new data we do not need that anymore // as we have clearly separated that now. In case we have duplicate data for a months we use the static data. private static class DaoEconomyHistoricalData { // Key is start date of the cycle in epoch seconds, value is reimbursement amount public final static Map REIMBURSEMENTS_BY_CYCLE_DATE = new HashMap<>(); public final static Map COMPENSATIONS_BY_CYCLE_DATE = new HashMap<>(); - public final static Map TOTAL_SUPPLY_BY_CYCLE_DATE = new HashMap<>(); static { REIMBURSEMENTS_BY_CYCLE_DATE.put(1571349571L, 60760L); @@ -420,8 +403,6 @@ public class DaoChartDataModel extends ChartDataModel { COMPENSATIONS_BY_CYCLE_DATE.put(1599175867L, 6086442L); COMPENSATIONS_BY_CYCLE_DATE.put(1601861442L, 5615973L); COMPENSATIONS_BY_CYCLE_DATE.put(1604845863L, 7782667L); - - TOTAL_SUPPLY_BY_CYCLE_DATE.put(1555340856L, 372540100L); } } } diff --git a/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/dao/DaoChartView.java b/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/dao/DaoChartView.java index 960a503257..d7c3d5c12a 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/dao/DaoChartView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/dao/DaoChartView.java @@ -50,7 +50,7 @@ public class DaoChartView extends ChartView { private final LongProperty proofOfBurnAmountProperty = new SimpleLongProperty(); private XYChart.Series seriesBsqTradeFee, seriesProofOfBurn, seriesCompensation, - seriesReimbursement, seriesTotalSupply, seriesTotalIssued, seriesTotalBurned, + seriesReimbursement, seriesTotalSupply, seriesSupplyChange, seriesTotalIssued, seriesTotalBurned, seriesTotalTradeFees, seriesProofOfBurnFromBtcFees, seriesProofOfBurnFromArbitration, seriesArbitrationDiff, seriesReimbursementAfterTagging, seriesBsqTradeFeeAfterTagging; @@ -111,7 +111,7 @@ public class DaoChartView extends ChartView { } protected Collection> getSeriesForLegend5() { - return List.of(seriesTotalSupply); + return List.of(seriesSupplyChange, seriesTotalSupply); } @@ -182,6 +182,10 @@ public class DaoChartView extends ChartView { seriesBsqTradeFeeAfterTagging = new XYChart.Series<>(); seriesBsqTradeFeeAfterTagging.setName(Res.get("dao.factsAndFigures.supply.bsqTradeFeeAfterTagging")); seriesIndexMap.put(getSeriesId(seriesBsqTradeFeeAfterTagging), 12); + + seriesSupplyChange = new XYChart.Series<>(); + seriesSupplyChange.setName(Res.get("dao.factsAndFigures.supply.supplyChange")); + seriesIndexMap.put(getSeriesId(seriesSupplyChange), 13); } @Override @@ -209,7 +213,11 @@ public class DaoChartView extends ChartView { tooltip.setShowDelay(Duration.millis(100)); Tooltip.install(toggle, tooltip); } else if (series.equals(seriesTotalSupply)) { - Tooltip tooltip = new Tooltip(Res.get("dao.factsAndFigures.supply.totalSupply.tooltip")); + Tooltip tooltip = new Tooltip(Res.get("dao.factsAndFigures.supply.supplyChange.tooltip")); + tooltip.setShowDelay(Duration.millis(100)); + Tooltip.install(toggle, tooltip); + } else if (series.equals(seriesSupplyChange)) { + Tooltip tooltip = new Tooltip(Res.get("dao.factsAndFigures.supply.supplyChange.tooltip")); tooltip.setShowDelay(Duration.millis(100)); Tooltip.install(toggle, tooltip); } @@ -258,6 +266,11 @@ public class DaoChartView extends ChartView { allFutures.add(future); applyTotalSupply(future); } + if (activeSeries.contains(seriesSupplyChange)) { + CompletableFuture future = new CompletableFuture<>(); + allFutures.add(future); + applySupplyChange(future); + } if (activeSeries.contains(seriesTotalTradeFees)) { CompletableFuture future = new CompletableFuture<>(); allFutures.add(future); @@ -338,6 +351,15 @@ public class DaoChartView extends ChartView { })); } + private void applySupplyChange(CompletableFuture completeFuture) { + model.getSupplyChangeChartData() + .whenComplete((data, t) -> + mapToUserThread(() -> { + seriesSupplyChange.getData().setAll(data); + completeFuture.complete(true); + })); + } + private void applyTotalTradeFees(CompletableFuture completeFuture) { model.getTotalTradeFeesChartData() .whenComplete((data, t) -> diff --git a/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/dao/DaoChartViewModel.java b/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/dao/DaoChartViewModel.java index 5e496f846c..3a64ffe3c0 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/dao/DaoChartViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/dao/DaoChartViewModel.java @@ -58,6 +58,10 @@ public class DaoChartViewModel extends ChartViewModel { return CompletableFuture.supplyAsync(() -> toChartData(dataModel.getTotalSupplyByInterval())); } + CompletableFuture>> getSupplyChangeChartData() { + return CompletableFuture.supplyAsync(() -> toChartData(dataModel.getSupplyChangeByInterval())); + } + CompletableFuture>> getTotalTradeFeesChartData() { return CompletableFuture.supplyAsync(() -> toChartData(dataModel.getTotalTradeFeesByInterval())); } diff --git a/desktop/src/main/java/bisq/desktop/theme-dark.css b/desktop/src/main/java/bisq/desktop/theme-dark.css index f24408a0ac..529f6555fa 100644 --- a/desktop/src/main/java/bisq/desktop/theme-dark.css +++ b/desktop/src/main/java/bisq/desktop/theme-dark.css @@ -152,6 +152,7 @@ -bs-chart-dao-line11: #ff3939; -bs-chart-dao-line12: #1a6b66; -bs-chart-dao-line13: #b6239c; + -bs-chart-dao-line14: #0052ff; /* Monero orange color code */ -xmr-orange: #f26822; diff --git a/desktop/src/main/java/bisq/desktop/theme-light.css b/desktop/src/main/java/bisq/desktop/theme-light.css index 244a1dccd0..067b1557f5 100644 --- a/desktop/src/main/java/bisq/desktop/theme-light.css +++ b/desktop/src/main/java/bisq/desktop/theme-light.css @@ -119,6 +119,7 @@ -bs-chart-dao-line11: #ff3939; -bs-chart-dao-line12: #1a6b66; -bs-chart-dao-line13: #b6239c; + -bs-chart-dao-line14: #0052ff; /* Monero orange color code */ -xmr-orange: #f26822;