diff --git a/crates/brk_computer/src/blocks/difficulty/vecs.rs b/crates/brk_computer/src/blocks/difficulty/vecs.rs index 06b90d83c..c164b1454 100644 --- a/crates/brk_computer/src/blocks/difficulty/vecs.rs +++ b/crates/brk_computer/src/blocks/difficulty/vecs.rs @@ -5,7 +5,7 @@ use vecdb::{Rw, StorageMode}; use crate::internal::{LazyPerBlock, PerBlock, Resolutions, PercentPerBlock}; #[derive(Traversable)] pub struct Vecs { - pub base: Resolutions, + pub value: Resolutions, pub as_hash: LazyPerBlock, pub adjustment: PercentPerBlock, pub epoch: PerBlock, diff --git a/crates/brk_computer/src/indexes/day3.rs b/crates/brk_computer/src/indexes/day3.rs index 9f601ce5d..58a4632bf 100644 --- a/crates/brk_computer/src/indexes/day3.rs +++ b/crates/brk_computer/src/indexes/day3.rs @@ -14,7 +14,7 @@ impl Vecs { pub(crate) fn forced_import(db: &Database, version: Version) -> Result { Ok(Self { identity: EagerVec::forced_import(db, "day3_index", version)?, - first_height: EagerVec::forced_import(db, "day3_first_height", version)?, + first_height: EagerVec::forced_import(db, "first_height", version)?, }) } } diff --git a/crates/brk_computer/src/indexes/hour1.rs b/crates/brk_computer/src/indexes/hour1.rs index 8f1c6c0c2..a67c6b951 100644 --- a/crates/brk_computer/src/indexes/hour1.rs +++ b/crates/brk_computer/src/indexes/hour1.rs @@ -14,7 +14,7 @@ impl Vecs { pub(crate) fn forced_import(db: &Database, version: Version) -> Result { Ok(Self { identity: EagerVec::forced_import(db, "hour1_index", version)?, - first_height: EagerVec::forced_import(db, "hour1_first_height", version)?, + first_height: EagerVec::forced_import(db, "first_height", version)?, }) } } diff --git a/crates/brk_computer/src/indexes/hour12.rs b/crates/brk_computer/src/indexes/hour12.rs index 2c5cb4721..e83b807ad 100644 --- a/crates/brk_computer/src/indexes/hour12.rs +++ b/crates/brk_computer/src/indexes/hour12.rs @@ -14,7 +14,7 @@ impl Vecs { pub(crate) fn forced_import(db: &Database, version: Version) -> Result { Ok(Self { identity: EagerVec::forced_import(db, "hour12_index", version)?, - first_height: EagerVec::forced_import(db, "hour12_first_height", version)?, + first_height: EagerVec::forced_import(db, "first_height", version)?, }) } } diff --git a/crates/brk_computer/src/indexes/hour4.rs b/crates/brk_computer/src/indexes/hour4.rs index b278daeda..174054943 100644 --- a/crates/brk_computer/src/indexes/hour4.rs +++ b/crates/brk_computer/src/indexes/hour4.rs @@ -14,7 +14,7 @@ impl Vecs { pub(crate) fn forced_import(db: &Database, version: Version) -> Result { Ok(Self { identity: EagerVec::forced_import(db, "hour4_index", version)?, - first_height: EagerVec::forced_import(db, "hour4_first_height", version)?, + first_height: EagerVec::forced_import(db, "first_height", version)?, }) } } diff --git a/crates/brk_computer/src/indexes/minute10.rs b/crates/brk_computer/src/indexes/minute10.rs index f7e86d5ac..7ed7b01b9 100644 --- a/crates/brk_computer/src/indexes/minute10.rs +++ b/crates/brk_computer/src/indexes/minute10.rs @@ -14,7 +14,7 @@ impl Vecs { pub(crate) fn forced_import(db: &Database, version: Version) -> Result { Ok(Self { identity: EagerVec::forced_import(db, "minute10_index", version)?, - first_height: EagerVec::forced_import(db, "minute10_first_height", version)?, + first_height: EagerVec::forced_import(db, "first_height", version)?, }) } } diff --git a/crates/brk_computer/src/indexes/minute30.rs b/crates/brk_computer/src/indexes/minute30.rs index 6fd38fc2e..80ef85d4a 100644 --- a/crates/brk_computer/src/indexes/minute30.rs +++ b/crates/brk_computer/src/indexes/minute30.rs @@ -14,7 +14,7 @@ impl Vecs { pub(crate) fn forced_import(db: &Database, version: Version) -> Result { Ok(Self { identity: EagerVec::forced_import(db, "minute30_index", version)?, - first_height: EagerVec::forced_import(db, "minute30_first_height", version)?, + first_height: EagerVec::forced_import(db, "first_height", version)?, }) } } diff --git a/crates/brk_computer/src/indexes/month1.rs b/crates/brk_computer/src/indexes/month1.rs index 1a79967e4..cef66df10 100644 --- a/crates/brk_computer/src/indexes/month1.rs +++ b/crates/brk_computer/src/indexes/month1.rs @@ -16,7 +16,7 @@ impl Vecs { Ok(Self { identity: EagerVec::forced_import(db, "month1_index", version)?, date: EagerVec::forced_import(db, "date", version)?, - first_height: EagerVec::forced_import(db, "month1_first_height", version)?, + first_height: EagerVec::forced_import(db, "first_height", version)?, }) } } diff --git a/crates/brk_computer/src/indexes/month3.rs b/crates/brk_computer/src/indexes/month3.rs index 00f128833..c9cac6ced 100644 --- a/crates/brk_computer/src/indexes/month3.rs +++ b/crates/brk_computer/src/indexes/month3.rs @@ -16,7 +16,7 @@ impl Vecs { Ok(Self { identity: EagerVec::forced_import(db, "month3_index", version)?, date: EagerVec::forced_import(db, "date", version)?, - first_height: EagerVec::forced_import(db, "month3_first_height", version)?, + first_height: EagerVec::forced_import(db, "first_height", version)?, }) } } diff --git a/crates/brk_computer/src/indexes/month6.rs b/crates/brk_computer/src/indexes/month6.rs index 3feb33196..433c22e8a 100644 --- a/crates/brk_computer/src/indexes/month6.rs +++ b/crates/brk_computer/src/indexes/month6.rs @@ -16,7 +16,7 @@ impl Vecs { Ok(Self { identity: EagerVec::forced_import(db, "month6_index", version)?, date: EagerVec::forced_import(db, "date", version)?, - first_height: EagerVec::forced_import(db, "month6_first_height", version)?, + first_height: EagerVec::forced_import(db, "first_height", version)?, }) } } diff --git a/crates/brk_computer/src/indexes/week1.rs b/crates/brk_computer/src/indexes/week1.rs index 00a059df3..2904b4c05 100644 --- a/crates/brk_computer/src/indexes/week1.rs +++ b/crates/brk_computer/src/indexes/week1.rs @@ -16,7 +16,7 @@ impl Vecs { Ok(Self { identity: EagerVec::forced_import(db, "week1_index", version)?, date: EagerVec::forced_import(db, "date", version)?, - first_height: EagerVec::forced_import(db, "week1_first_height", version)?, + first_height: EagerVec::forced_import(db, "first_height", version)?, }) } } diff --git a/crates/brk_computer/src/indexes/year1.rs b/crates/brk_computer/src/indexes/year1.rs index eaacddca0..65fcc54df 100644 --- a/crates/brk_computer/src/indexes/year1.rs +++ b/crates/brk_computer/src/indexes/year1.rs @@ -16,7 +16,7 @@ impl Vecs { Ok(Self { identity: EagerVec::forced_import(db, "year1_index", version)?, date: EagerVec::forced_import(db, "date", version)?, - first_height: EagerVec::forced_import(db, "year1_first_height", version)?, + first_height: EagerVec::forced_import(db, "first_height", version)?, }) } } diff --git a/crates/brk_computer/src/indexes/year10.rs b/crates/brk_computer/src/indexes/year10.rs index 36d368e46..6b3ec9668 100644 --- a/crates/brk_computer/src/indexes/year10.rs +++ b/crates/brk_computer/src/indexes/year10.rs @@ -16,7 +16,7 @@ impl Vecs { Ok(Self { identity: EagerVec::forced_import(db, "year10_index", version)?, date: EagerVec::forced_import(db, "date", version)?, - first_height: EagerVec::forced_import(db, "year10_first_height", version)?, + first_height: EagerVec::forced_import(db, "first_height", version)?, }) } } diff --git a/crates/brk_indexer/src/vecs/blocks.rs b/crates/brk_indexer/src/vecs/blocks.rs index 52b57deff..a5fbc4978 100644 --- a/crates/brk_indexer/src/vecs/blocks.rs +++ b/crates/brk_indexer/src/vecs/blocks.rs @@ -11,7 +11,7 @@ use crate::parallel_import; #[derive(Traversable)] pub struct BlocksVecs { pub blockhash: M::Stored>, - #[traversable(wrap = "difficulty", rename = "raw")] + #[traversable(wrap = "difficulty", rename = "value")] pub difficulty: M::Stored>, /// Doesn't guarantee continuity due to possible reorgs and more generally the nature of mining #[traversable(wrap = "time")] diff --git a/crates/brk_query/src/impl/mining/epochs.rs b/crates/brk_query/src/impl/mining/epochs.rs index 3330a74a0..c0376cfb6 100644 --- a/crates/brk_query/src/impl/mining/epochs.rs +++ b/crates/brk_query/src/impl/mining/epochs.rs @@ -23,7 +23,7 @@ pub fn iter_difficulty_epochs( let epoch_to_height = &computer.indexes.epoch.first_height; let epoch_to_timestamp = &computer.blocks.time.timestamp.epoch; - let epoch_to_difficulty = &computer.blocks.difficulty.base.epoch; + let epoch_to_difficulty = &computer.blocks.difficulty.value.epoch; let mut results = Vec::with_capacity(end_epoch.to_usize() - start_epoch.to_usize() + 1); let mut prev_difficulty: Option = None; diff --git a/website/scripts/options/market.js b/website/scripts/options/market.js index ad5202f64..d408c8d51 100644 --- a/website/scripts/options/market.js +++ b/website/scripts/options/market.js @@ -5,20 +5,27 @@ import { brk } from "../client.js"; import { includes } from "../utils/array.js"; import { Unit } from "../utils/units.js"; import { priceLine, priceLines } from "./constants.js"; -import { baseline, histogram, line, price } from "./series.js"; +import { + baseline, + histogram, + line, + price, + percentRatio, + percentRatioBaseline, +} from "./series.js"; import { periodIdToName } from "./utils.js"; /** * @typedef {Object} Period * @property {string} id * @property {Color} color - * @property {AnyMetricPattern} returns + * @property {{ percent: AnyMetricPattern, ratio: AnyMetricPattern }} returns * @property {AnyPricePattern} lookback * @property {boolean} [defaultActive] */ /** - * @typedef {Period & { cagr: AnyMetricPattern }} PeriodWithCagr + * @typedef {Period & { cagr: { percent: AnyMetricPattern, ratio: AnyMetricPattern } }} PeriodWithCagr */ /** @@ -442,14 +449,11 @@ export function createMarketSection() { name: "Drawdown", title: "ATH Drawdown", top: [price({ metric: ath.high, name: "ATH" })], - bottom: [ - line({ - metric: ath.drawdown.percent, - name: "Drawdown", - color: colors.loss, - unit: Unit.percentage, - }), - ], + bottom: percentRatio({ + pattern: ath.drawdown, + name: "Drawdown", + color: colors.loss, + }), }, { name: "Time Since", @@ -535,11 +539,10 @@ export function createMarketSection() { name: "Choppiness", title: "Choppiness Index", bottom: [ - line({ - metric: range.choppinessIndex2w.percent, + ...percentRatio({ + pattern: range.choppinessIndex2w, name: "2w", color: colors.indicator.main, - unit: Unit.index, }), ...priceLines({ unit: Unit.index, numbers: [61.8, 38.2] }), ], @@ -1182,14 +1185,11 @@ export function createMarketSection() { { name: "Gini", title: "Gini Coefficient", - bottom: [ - line({ - metric: indicators.gini.percent, - name: "Gini", - color: colors.loss, - unit: Unit.ratio, - }), - ], + bottom: percentRatio({ + pattern: indicators.gini, + name: "Gini", + color: colors.loss, + }), }, { name: "RHODL Ratio", diff --git a/website/scripts/options/network.js b/website/scripts/options/network.js index 7b6d1ab94..1ee18d645 100644 --- a/website/scripts/options/network.js +++ b/website/scripts/options/network.js @@ -12,12 +12,15 @@ import { fromSupplyPattern, chartsFromFullPerBlock, chartsFromCount, - fromStatsPattern, chartsFromSumPerBlock, rollingWindowsTree, distributionWindowsTree, mapWindows, ROLLING_WINDOWS, + chartsFromBlockAnd6b, + percentRatio, + percentRatioDots, + rollingPercentRatioTree, } from "./series.js"; import { satsBtcUsd, satsBtcUsdFrom } from "./shared.js"; @@ -236,10 +239,9 @@ export function createNetworkSection() { }), ], }, - rollingWindowsTree({ - windows: mapWindows(addresses.delta[key].rate, (r) => r.ratio), + rollingPercentRatioTree({ + windows: addresses.delta[key].rate, title: `${titlePrefix}Address Growth Rate`, - unit: Unit.ratio, }), ], }, @@ -351,12 +353,11 @@ export function createNetworkSection() { tree: ROLLING_WINDOWS.map((w) => ({ name: w.name, title: `${groupName} Address Growth Rate (${w.name})`, - bottom: types.map((t) => - line({ - metric: addresses.delta[t.key].rate[w.key].ratio, + bottom: types.flatMap((t) => + percentRatio({ + pattern: addresses.delta[t.key].rate[w.key], name: t.name, color: t.color, - unit: Unit.ratio, }), ), })), @@ -465,19 +466,58 @@ export function createNetworkSection() { { name: "Inflation", title: "Inflation Rate", - bottom: [ - dots({ - metric: supply.inflationRate.percent, - name: "Rate", - unit: Unit.percentage, + bottom: percentRatioDots({ + pattern: supply.inflationRate, + name: "Rate", + }), + }, + { + name: "Market Cap", + tree: [ + { + name: "Base", + title: "Market Cap", + bottom: [ + line({ + metric: supply.marketCap.usd, + name: "Market Cap", + unit: Unit.usd, + }), + ], + }, + rollingWindowsTree({ + windows: mapWindows( + supply.marketCap.delta.change, + (c) => c.usd, + ), + title: "Market Cap Change", + unit: Unit.usd, + series: baseline, + }), + rollingPercentRatioTree({ + windows: supply.marketCap.delta.rate, + title: "Market Cap Growth Rate", }), ], }, + { + name: "Hodled or Lost", + title: "Hodled or Lost Supply", + bottom: satsBtcUsd({ + pattern: supply.hodledOrLost, + name: "Supply", + }), + }, + rollingWindowsTree({ + windows: supply.marketMinusRealizedCapGrowthRate, + title: "Market - Realized Cap Growth Rate", + unit: Unit.ratio, + }), { name: "Unspendable", tree: [ { - name: "Sum", + name: "Base", title: "Unspendable Supply", bottom: satsBtcUsdFrom({ source: supply.burned.unspendable, @@ -485,6 +525,31 @@ export function createNetworkSection() { name: "sum", }), }, + { + name: "Rolling", + tree: [ + { + name: "Compare", + title: "Unspendable Supply Rolling", + bottom: ROLLING_WINDOWS.flatMap((w) => + satsBtcUsd({ + pattern: supply.burned.unspendable.sum[w.key], + name: w.name, + color: w.color, + }), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `Unspendable Supply ${w.name}`, + bottom: satsBtcUsd({ + pattern: supply.burned.unspendable.sum[w.key], + name: w.name, + color: w.color, + }), + })), + ], + }, { name: "Cumulative", title: "Unspendable Supply (Total)", @@ -500,14 +565,45 @@ export function createNetworkSection() { name: "OP_RETURN", tree: [ { - name: "Sum", + name: "Base", title: "OP_RETURN Burned", - bottom: satsBtcUsd({ pattern: scripts.value.opreturn.base, name: "sum" }), + bottom: satsBtcUsd({ + pattern: supply.burned.opreturn.base, + name: "sum", + }), + }, + { + name: "Rolling", + tree: [ + { + name: "Compare", + title: "OP_RETURN Burned Rolling", + bottom: ROLLING_WINDOWS.flatMap((w) => + satsBtcUsd({ + pattern: supply.burned.opreturn.sum[w.key], + name: w.name, + color: w.color, + }), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `OP_RETURN Burned ${w.name}`, + bottom: satsBtcUsd({ + pattern: supply.burned.opreturn.sum[w.key], + name: w.name, + color: w.color, + }), + })), + ], }, { name: "Cumulative", title: "OP_RETURN Burned (Total)", - bottom: satsBtcUsd({ pattern: scripts.value.opreturn.cumulative, name: "all-time" }), + bottom: satsBtcUsd({ + pattern: supply.burned.opreturn.cumulative, + name: "all-time", + }), }, ], }, @@ -527,12 +623,25 @@ export function createNetworkSection() { }), }, { - name: "Fee Rate", - title: "Transaction Fee Rate", - bottom: fromStatsPattern({ - pattern: transactions.fees.feeRate.block, - unit: Unit.feeRate, - }), + name: "Fees", + tree: [ + { + name: "Fee Rate", + tree: chartsFromBlockAnd6b({ + pattern: transactions.fees.feeRate, + title: "Transaction Fee Rate", + unit: Unit.feeRate, + }), + }, + { + name: "Fee", + tree: chartsFromBlockAnd6b({ + pattern: transactions.fees.fee, + title: "Transaction Fee", + unit: Unit.sats, + }), + }, + ], }, { name: "Volume", @@ -553,27 +662,92 @@ export function createNetworkSection() { ], }, { - name: "Annualized", - title: "Annualized Transaction Volume", + name: "Sent Rolling", + tree: [ + { + name: "Compare", + title: "Sent Volume Rolling", + bottom: ROLLING_WINDOWS.flatMap((w) => + satsBtcUsd({ + pattern: transactions.volume.sentSum.sum[w.key], + name: w.name, + color: w.color, + }), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `Sent Volume ${w.name}`, + bottom: satsBtcUsd({ + pattern: transactions.volume.sentSum.sum[w.key], + name: w.name, + color: w.color, + }), + })), + ], + }, + { + name: "Sent Cumulative", + title: "Sent Volume (Total)", bottom: satsBtcUsd({ - pattern: transactions.volume.sentSum.sum._1y, - name: "Annualized", + pattern: transactions.volume.sentSum.cumulative, + name: "all-time", + }), + }, + { + name: "Received Rolling", + tree: [ + { + name: "Compare", + title: "Received Volume Rolling", + bottom: ROLLING_WINDOWS.flatMap((w) => + satsBtcUsd({ + pattern: transactions.volume.receivedSum.sum[w.key], + name: w.name, + color: w.color, + }), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `Received Volume ${w.name}`, + bottom: satsBtcUsd({ + pattern: transactions.volume.receivedSum.sum[w.key], + name: w.name, + color: w.color, + }), + })), + ], + }, + { + name: "Received Cumulative", + title: "Received Volume (Total)", + bottom: satsBtcUsd({ + pattern: transactions.volume.receivedSum.cumulative, + name: "all-time", }), }, ], }, { name: "Size", - title: "Transaction Size", - bottom: [ - ...fromStatsPattern({ - pattern: transactions.size.weight.block, - unit: Unit.wu, - }), - ...fromStatsPattern({ - pattern: transactions.size.vsize.block, - unit: Unit.vb, - }), + tree: [ + { + name: "Weight", + tree: chartsFromBlockAnd6b({ + pattern: transactions.size.weight, + title: "Transaction Weight", + unit: Unit.wu, + }), + }, + { + name: "vSize", + tree: chartsFromBlockAnd6b({ + pattern: transactions.size.vsize, + title: "Transaction vSize", + unit: Unit.vb, + }), + }, ], }, { @@ -592,6 +766,39 @@ export function createNetworkSection() { }), ), }, + { + name: "Rolling", + tree: [ + { + name: "Compare", + title: "Transaction Versions Rolling", + bottom: entries(transactions.versions).flatMap( + ([v, data], i, arr) => + ROLLING_WINDOWS.map((w) => + line({ + metric: data.sum[w.key], + name: `${v} ${w.name}`, + color: colors.at(i, arr.length), + unit: Unit.count, + }), + ), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `Transaction Versions (${w.name})`, + bottom: entries(transactions.versions).map( + ([v, data], i, arr) => + line({ + metric: data.sum[w.key], + name: v, + color: colors.at(i, arr.length), + unit: Unit.count, + }), + ), + })), + ], + }, { name: "Cumulative", title: "Transaction Versions (Total)", @@ -815,12 +1022,33 @@ export function createNetworkSection() { { name: "Fullness", title: "Block Fullness", - bottom: [ - dots({ - metric: blocks.fullness.percent, - name: "base", - unit: Unit.percentage, - }), + bottom: percentRatioDots({ + pattern: blocks.fullness, + name: "base", + }), + }, + { + name: "Difficulty", + tree: [ + { + name: "Base", + title: "Mining Difficulty", + bottom: [ + line({ + metric: blocks.difficulty.base, + name: "Difficulty", + unit: Unit.count, + }), + ], + }, + { + name: "Adjustment", + title: "Difficulty Adjustment", + bottom: percentRatioDots({ + pattern: blocks.difficulty.adjustment, + name: "Adjustment", + }), + }, ], }, ], @@ -996,12 +1224,11 @@ export function createNetworkSection() { tree: ROLLING_WINDOWS.map((w) => ({ name: w.name, title: `Address Growth Rate by Type (${w.name})`, - bottom: addressTypes.map((t) => - line({ - metric: addresses.delta[t.key].rate[w.key].ratio, + bottom: addressTypes.flatMap((t) => + percentRatio({ + pattern: addresses.delta[t.key].rate[w.key], name: t.name, color: t.color, - unit: Unit.ratio, defaultActive: t.defaultActive, }), ), @@ -1218,12 +1445,24 @@ export function createNetworkSection() { color: colors.segwit, unit: Unit.percentage, }), + line({ + metric: scripts.adoption.segwit.ratio, + name: "SegWit", + color: colors.segwit, + unit: Unit.ratio, + }), line({ metric: scripts.adoption.taproot.percent, name: "Taproot", color: taprootAddresses[1].color, unit: Unit.percentage, }), + line({ + metric: scripts.adoption.taproot.ratio, + name: "Taproot", + color: taprootAddresses[1].color, + unit: Unit.ratio, + }), ], }, { @@ -1235,6 +1474,11 @@ export function createNetworkSection() { name: "Adoption", unit: Unit.percentage, }), + line({ + metric: scripts.adoption.segwit.ratio, + name: "Adoption", + unit: Unit.ratio, + }), ], }, { @@ -1246,6 +1490,11 @@ export function createNetworkSection() { name: "Adoption", unit: Unit.percentage, }), + line({ + metric: scripts.adoption.taproot.ratio, + name: "Adoption", + unit: Unit.ratio, + }), ], }, ], diff --git a/website/scripts/options/series.js b/website/scripts/options/series.js index 69f2b48c3..6ea5fc08c 100644 --- a/website/scripts/options/series.js +++ b/website/scripts/options/series.js @@ -599,6 +599,86 @@ export function fromSupplyPattern({ pattern, title, color }) { ]; } +// ============================================================================ +// Percent + Ratio helpers +// ============================================================================ + +/** + * Create percent + ratio series from a BpsPercentRatioPattern + * @param {Object} args + * @param {{ percent: AnyMetricPattern, ratio: AnyMetricPattern }} args.pattern + * @param {string} args.name + * @param {Color} [args.color] + * @param {boolean} [args.defaultActive] + * @returns {AnyFetchedSeriesBlueprint[]} + */ +export function percentRatio({ pattern, name, color, defaultActive }) { + return [ + line({ metric: pattern.percent, name, color, defaultActive, unit: Unit.percentage }), + line({ metric: pattern.ratio, name, color, defaultActive, unit: Unit.ratio }), + ]; +} + +/** + * Create percent + ratio dots series from a BpsPercentRatioPattern + * @param {Object} args + * @param {{ percent: AnyMetricPattern, ratio: AnyMetricPattern }} args.pattern + * @param {string} args.name + * @param {Color} [args.color] + * @param {boolean} [args.defaultActive] + * @returns {AnyFetchedSeriesBlueprint[]} + */ +export function percentRatioDots({ pattern, name, color, defaultActive }) { + return [ + dots({ metric: pattern.percent, name, color, defaultActive, unit: Unit.percentage }), + dots({ metric: pattern.ratio, name, color, defaultActive, unit: Unit.ratio }), + ]; +} + +/** + * Create percent + ratio baseline series from a BpsPercentRatioPattern + * @param {Object} args + * @param {{ percent: AnyMetricPattern, ratio: AnyMetricPattern }} args.pattern + * @param {string} args.name + * @param {Color} [args.color] + * @param {boolean} [args.defaultActive] + * @returns {AnyFetchedSeriesBlueprint[]} + */ +export function percentRatioBaseline({ pattern, name, color, defaultActive }) { + return [ + baseline({ metric: pattern.percent, name, color, defaultActive, unit: Unit.percentage }), + baseline({ metric: pattern.ratio, name, color, defaultActive, unit: Unit.ratio }), + ]; +} + +/** + * Create a Rolling folder tree where each window is a BpsPercentRatioPattern (percent + ratio) + * @param {Object} args + * @param {{ _24h: { percent: AnyMetricPattern, ratio: AnyMetricPattern }, _1w: { percent: AnyMetricPattern, ratio: AnyMetricPattern }, _1m: { percent: AnyMetricPattern, ratio: AnyMetricPattern }, _1y: { percent: AnyMetricPattern, ratio: AnyMetricPattern } }} args.windows + * @param {string} args.title + * @param {(args: {pattern: { percent: AnyMetricPattern, ratio: AnyMetricPattern }, name: string, color: Color}) => AnyFetchedSeriesBlueprint[]} [args.series] + * @returns {PartialOptionsGroup} + */ +export function rollingPercentRatioTree({ windows, title, series = percentRatio }) { + return { + name: "Rolling", + tree: [ + { + name: "Compare", + title: `${title} Rolling`, + bottom: ROLLING_WINDOWS.flatMap((w) => + series({ pattern: windows[w.key], name: w.name, color: w.color }), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `${title} ${w.name}`, + bottom: series({ pattern: windows[w.key], name: w.name, color: w.color }), + })), + ], + }; +} + // ============================================================================ // Chart-generating helpers (return PartialOptionsTree for folder structures) // ============================================================================ @@ -798,6 +878,29 @@ export function chartsFromSum({ export const chartsFromSumPerBlock = (args) => chartsFromSum({ ...args, distributionSuffix: "per Block" }); +/** + * Create Per Block + Per 6 Blocks stats charts from a _6bBlockTxPattern + * @param {Object} args + * @param {{ block: DistributionStats, _6b: DistributionStats }} args.pattern + * @param {string} args.title + * @param {Unit} args.unit + * @returns {PartialOptionsTree} + */ +export function chartsFromBlockAnd6b({ pattern, title, unit }) { + return [ + { + name: "Per Block", + title: `${title} per Block`, + bottom: fromStatsPattern({ pattern: pattern.block, unit }), + }, + { + name: "Per 6 Blocks", + title: `${title} per 6 Blocks`, + bottom: fromStatsPattern({ pattern: pattern._6b, unit }), + }, + ]; +} + /** * Split pattern with rolling sum windows + cumulative into charts * @param {Object} args @@ -809,6 +912,11 @@ export const chartsFromSumPerBlock = (args) => */ export function chartsFromCount({ pattern, title, unit, color }) { return [ + { + name: "Base", + title, + bottom: [{ metric: pattern.base, title: "base", color, unit }], + }, rollingWindowsTree({ windows: pattern.sum, title, unit }), { name: "Cumulative",