mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-25 23:29:58 -07:00
global: snapshot
This commit is contained in:
@@ -22,7 +22,7 @@ import {
|
||||
satsBtcUsd,
|
||||
satsBtcUsdFullTree,
|
||||
mapCohortsWithAll,
|
||||
groupedWindowsCumulative,
|
||||
groupedWindowsCumulativeWithAll,
|
||||
groupedWindowsCumulativeSatsBtcUsd,
|
||||
} from "../shared.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
@@ -481,7 +481,7 @@ function groupedVolumeFolderWithAdjusted(list, all, title, getTransferVolume, ge
|
||||
...groupedVolumeTree(list, all, title, getTransferVolume),
|
||||
{
|
||||
name: "Adjusted",
|
||||
tree: groupedWindowsCumulative({
|
||||
tree: groupedWindowsCumulativeWithAll({
|
||||
list, all, title, metricTitle: "Adjusted Transfer Volume",
|
||||
getWindowSeries: (c, key) => getAdjustedTransferVolume(c).sum[key],
|
||||
getCumulativeSeries: (c) => getAdjustedTransferVolume(c).cumulative,
|
||||
@@ -532,7 +532,7 @@ function groupedSoprCharts(list, all, getRatio, title, prefix = "") {
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedValueDestroyedTree(list, all, title, getValueDestroyed) {
|
||||
return groupedWindowsCumulative({
|
||||
return groupedWindowsCumulativeWithAll({
|
||||
list, all, title, metricTitle: "Value Destroyed",
|
||||
getWindowSeries: (c, key) => getValueDestroyed(c).sum[key],
|
||||
getCumulativeSeries: (c) => getValueDestroyed(c).cumulative,
|
||||
@@ -637,7 +637,7 @@ function groupedActivitySharedItems(list, all, title) {
|
||||
return [
|
||||
{
|
||||
name: "Coindays Destroyed",
|
||||
tree: groupedWindowsCumulative({
|
||||
tree: groupedWindowsCumulativeWithAll({
|
||||
list,
|
||||
all,
|
||||
title,
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
flatMapCohorts,
|
||||
mapCohortsWithAll,
|
||||
flatMapCohortsWithAll,
|
||||
groupedWindowsCumulative,
|
||||
groupedWindowsCumulativeWithAll,
|
||||
} from "../shared.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { priceLines } from "../constants.js";
|
||||
@@ -67,7 +67,7 @@ function groupedOutputsFolder(list, all, title) {
|
||||
},
|
||||
{
|
||||
name: "Spent",
|
||||
tree: groupedWindowsCumulative({
|
||||
tree: groupedWindowsCumulativeWithAll({
|
||||
list, all, title, metricTitle: "Spent UTXO Count",
|
||||
getWindowSeries: (c, key) => c.tree.outputs.spentCount.sum[key],
|
||||
getCumulativeSeries: (c) => c.tree.outputs.spentCount.cumulative,
|
||||
|
||||
@@ -1059,7 +1059,7 @@ export function createMarketSection() {
|
||||
title: "Dormancy",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.dormancy.supplyAdjusted,
|
||||
series: indicators.dormancy.supplyAdj,
|
||||
name: "Supply Adjusted",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
@@ -1090,7 +1090,7 @@ export function createMarketSection() {
|
||||
title: "Coindays Destroyed (Supply Adjusted)",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.coindaysDestroyedSupplyAdjusted,
|
||||
series: indicators.coindaysDestroyedSupplyAdj,
|
||||
name: "CDD SA",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
@@ -1102,7 +1102,7 @@ export function createMarketSection() {
|
||||
title: "Coinyears Destroyed (Supply Adjusted)",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.coinyearsDestroyedSupplyAdjusted,
|
||||
series: indicators.coinyearsDestroyedSupplyAdj,
|
||||
name: "CYD SA",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
chartsFromCount,
|
||||
chartsFromCountEntries,
|
||||
chartsFromPercentCumulative,
|
||||
chartsFromPercentCumulativeEntries,
|
||||
chartsFromAggregatedPerBlock,
|
||||
averagesArray,
|
||||
simpleDeltaTree,
|
||||
@@ -25,6 +26,7 @@ import {
|
||||
satsBtcUsdFrom,
|
||||
satsBtcUsdFullTree,
|
||||
formatCohortTitle,
|
||||
groupedWindowsCumulative,
|
||||
} from "./shared.js";
|
||||
|
||||
/**
|
||||
@@ -82,9 +84,10 @@ export function createNetworkSection() {
|
||||
|
||||
// Transacting types (transaction participation)
|
||||
const activityTypes = /** @type {const} */ ([
|
||||
{ key: "active", name: "Active" },
|
||||
{ key: "sending", name: "Sending" },
|
||||
{ key: "receiving", name: "Receiving" },
|
||||
{ key: "both", name: "Sending & Receiving" },
|
||||
{ key: "bidirectional", name: "Bidirectional" },
|
||||
{ key: "reactivated", name: "Reactivated" },
|
||||
]);
|
||||
|
||||
@@ -127,203 +130,419 @@ export function createNetworkSection() {
|
||||
{ key: "total", name: "Total", color: colors.default },
|
||||
]);
|
||||
|
||||
/**
|
||||
* @param {AddressableType | "all"} key
|
||||
* @param {string} [typeName]
|
||||
*/
|
||||
const createAddressSeriesTree = (key, typeName) => {
|
||||
const title = formatCohortTitle(typeName);
|
||||
return [
|
||||
const reusedSetEntries =
|
||||
/**
|
||||
* @param {AddressableType | "all"} key
|
||||
* @param {(name: string) => string} title
|
||||
*/
|
||||
(key, title) => [
|
||||
{
|
||||
name: "Count",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Address Count"),
|
||||
bottom: countMetrics.map((m) =>
|
||||
line({
|
||||
series: addrs[m.key][key],
|
||||
name: m.name,
|
||||
color: m.color,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
},
|
||||
...countMetrics.map((m) => ({
|
||||
name: m.name,
|
||||
title: title(`${m.name} Addresses`),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs[m.key][key],
|
||||
name: m.name,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
})),
|
||||
name: "Compare",
|
||||
title: title("Reused Address Count"),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs.reused.count.funded[key],
|
||||
name: "Funded",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
series: addrs.reused.count.total[key],
|
||||
name: "Total",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Reused",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Reused Address Count"),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs.reused.count.funded[key],
|
||||
name: "Funded",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
series: addrs.reused.count.total[key],
|
||||
name: "Total",
|
||||
name: "Funded",
|
||||
title: title("Funded Reused Addresses"),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs.reused.count.funded[key],
|
||||
name: "Funded Reused",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Total Reused Addresses"),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs.reused.count.total[key],
|
||||
name: "Total Reused",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const reusedInputsSubtree =
|
||||
/**
|
||||
* @param {AddressableType | "all"} key
|
||||
* @param {(name: string) => string} title
|
||||
*/
|
||||
(key, title) => ({
|
||||
name: "Inputs",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: chartsFromCount({
|
||||
pattern: addrs.reused.events.inputFromReusedAddrCount[key],
|
||||
title,
|
||||
metric: "Transaction Inputs from Reused Addresses",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
tree: chartsFromPercentCumulative({
|
||||
pattern: addrs.reused.events.inputFromReusedAddrShare[key],
|
||||
title,
|
||||
metric: "Share of Transaction Inputs from Reused Addresses",
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const reusedOutputsSubtreeForAll =
|
||||
/** @param {(name: string) => string} title */
|
||||
(title) => ({
|
||||
name: "Outputs",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: chartsFromCount({
|
||||
pattern: addrs.reused.events.outputToReusedAddrCount.all,
|
||||
title,
|
||||
metric: "Transaction Outputs to Reused Addresses",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
tree: chartsFromPercentCumulativeEntries({
|
||||
entries: [
|
||||
{
|
||||
name: "All",
|
||||
pattern: addrs.reused.events.outputToReusedAddrShare.all,
|
||||
},
|
||||
{
|
||||
name: "Spendable",
|
||||
pattern: addrs.reused.events.spendableOutputToReusedAddrShare,
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Funded",
|
||||
title: title("Funded Reused Addresses"),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs.reused.count.funded[key],
|
||||
name: "Funded Reused",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Total Reused Addresses"),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs.reused.count.total[key],
|
||||
name: "Total Reused",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Uses",
|
||||
tree: chartsFromCount({
|
||||
pattern: addrs.reused.uses.reusedAddrUseCount[key],
|
||||
title,
|
||||
metric: "Reused Address Uses",
|
||||
title,
|
||||
metric: "Share of Transaction Outputs to Reused Addresses",
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const reusedOutputsSubtreeForType =
|
||||
/**
|
||||
* @param {AddressableType} key
|
||||
* @param {(name: string) => string} title
|
||||
*/
|
||||
(key, title) => ({
|
||||
name: "Outputs",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: chartsFromCount({
|
||||
pattern: addrs.reused.events.outputToReusedAddrCount[key],
|
||||
title,
|
||||
metric: "Transaction Outputs to Reused Addresses",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
tree: chartsFromPercentCumulative({
|
||||
pattern: addrs.reused.events.outputToReusedAddrShare[key],
|
||||
title,
|
||||
metric: "Share of Transaction Outputs to Reused Addresses",
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const reusedActiveSubtreeForAll =
|
||||
/** @param {(name: string) => string} title */
|
||||
(title) => ({
|
||||
name: "Active",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: averagesArray({
|
||||
windows: addrs.reused.events.activeReusedAddrCount,
|
||||
title,
|
||||
metric: "Active Reused Addresses",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
tree: averagesArray({
|
||||
windows: addrs.reused.events.activeReusedAddrShare,
|
||||
title,
|
||||
metric: "Active Reused Address Share",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const reusedSubtreeForAll =
|
||||
/** @param {(name: string) => string} title */
|
||||
(title) => ({
|
||||
name: "Reused",
|
||||
tree: [
|
||||
...reusedSetEntries("all", title),
|
||||
reusedActiveSubtreeForAll(title),
|
||||
reusedOutputsSubtreeForAll(title),
|
||||
reusedInputsSubtree("all", title),
|
||||
],
|
||||
});
|
||||
|
||||
const reusedSubtreeForType =
|
||||
/**
|
||||
* @param {AddressableType} key
|
||||
* @param {(name: string) => string} title
|
||||
*/
|
||||
(key, title) => ({
|
||||
name: "Reused",
|
||||
tree: [
|
||||
...reusedSetEntries(key, title),
|
||||
reusedOutputsSubtreeForType(key, title),
|
||||
reusedInputsSubtree(key, title),
|
||||
],
|
||||
});
|
||||
|
||||
const countSubtree =
|
||||
/**
|
||||
* @param {AddressableType | "all"} key
|
||||
* @param {(name: string) => string} title
|
||||
*/
|
||||
(key, title) => ({
|
||||
name: "Count",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Address Count"),
|
||||
bottom: countMetrics.map((m) =>
|
||||
line({
|
||||
series: addrs[m.key][key],
|
||||
name: m.name,
|
||||
color: m.color,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
tree: chartsFromPercentCumulative({
|
||||
pattern: addrs.reused.uses.reusedAddrUsePercent[key],
|
||||
title,
|
||||
metric: "Share of Outputs to Reused Addresses",
|
||||
),
|
||||
},
|
||||
...countMetrics.map((m) => ({
|
||||
name: m.name,
|
||||
title: title(`${m.name} Addresses`),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs[m.key][key],
|
||||
name: m.name,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Exposed",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Quantum Exposed Address Count"),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs.exposed.count.funded[key],
|
||||
name: "Funded",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
series: addrs.exposed.count.total[key],
|
||||
name: "Total",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Funded",
|
||||
title: title("Funded Quantum Exposed Address Count"),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs.exposed.count.funded[key],
|
||||
name: "Funded Exposed",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Total Quantum Exposed Address Count"),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs.exposed.count.total[key],
|
||||
name: "Total Exposed",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
],
|
||||
})),
|
||||
],
|
||||
});
|
||||
|
||||
const exposedSubtree =
|
||||
/**
|
||||
* @param {AddressableType | "all"} key
|
||||
* @param {(name: string) => string} title
|
||||
*/
|
||||
(key, title) => ({
|
||||
name: "Exposed",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Exposed Address Count"),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs.exposed.count.funded[key],
|
||||
name: "Funded",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
series: addrs.exposed.count.total[key],
|
||||
name: "Total",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Funded",
|
||||
title: title("Funded Exposed Address Count"),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs.exposed.count.funded[key],
|
||||
name: "Funded Exposed",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Total Exposed Address Count"),
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs.exposed.count.total[key],
|
||||
name: "Total Exposed",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Supply",
|
||||
title: title("Supply in Exposed Addresses"),
|
||||
bottom: satsBtcUsd({
|
||||
pattern: addrs.exposed.supply[key],
|
||||
name: "Supply",
|
||||
title: title("Supply in Quantum Exposed Addresses"),
|
||||
bottom: satsBtcUsd({
|
||||
pattern: addrs.exposed.supply[key],
|
||||
name: "Supply",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
...simpleDeltaTree({
|
||||
delta: addrs.delta[key],
|
||||
title,
|
||||
metric: "Address Count",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
{
|
||||
name: "New",
|
||||
tree: chartsFromCount({
|
||||
pattern: addrs.new[key],
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const activityPerTypeEntries =
|
||||
/**
|
||||
* @param {AddressableType | "all"} key
|
||||
* @param {(name: string) => string} title
|
||||
*/
|
||||
(key, title) =>
|
||||
activityTypes.map((t) => ({
|
||||
name: t.name,
|
||||
tree: averagesArray({
|
||||
windows: addrs.activity[key][t.key],
|
||||
title,
|
||||
metric: "New Addresses",
|
||||
metric: `${t.name} Addresses`,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Activity",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`${w.title} Active Addresses`),
|
||||
bottom: activityTypes.map((t, i) =>
|
||||
}));
|
||||
|
||||
const activitySubtreeForAll =
|
||||
/** @param {(name: string) => string} title */
|
||||
(title) => ({
|
||||
name: "Activity",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`${w.title} Active Addresses`),
|
||||
bottom: [
|
||||
...activityTypes.map((t, i) =>
|
||||
line({
|
||||
series: addrs.activity[key][t.key][w.key],
|
||||
series: addrs.activity.all[t.key][w.key],
|
||||
name: t.name,
|
||||
color: colors.at(i, activityTypes.length),
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
},
|
||||
...activityTypes.map((t) => ({
|
||||
name: t.name,
|
||||
tree: averagesArray({
|
||||
windows: addrs.activity[key][t.key],
|
||||
title,
|
||||
metric: `${t.name} Addresses`,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
series: addrs.reused.events.activeReusedAddrShare[w.key],
|
||||
name: "Reused Share",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
})),
|
||||
],
|
||||
},
|
||||
...activityPerTypeEntries("all", title),
|
||||
],
|
||||
});
|
||||
|
||||
const activitySubtreeForType =
|
||||
/**
|
||||
* @param {AddressableType} key
|
||||
* @param {(name: string) => string} title
|
||||
*/
|
||||
(key, title) => ({
|
||||
name: "Activity",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`${w.title} Active Addresses`),
|
||||
bottom: activityTypes.map((t, i) =>
|
||||
line({
|
||||
series: addrs.activity[key][t.key][w.key],
|
||||
name: t.name,
|
||||
color: colors.at(i, activityTypes.length),
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
},
|
||||
...activityPerTypeEntries(key, title),
|
||||
],
|
||||
});
|
||||
|
||||
const createAddressSeriesTreeForAll = () => {
|
||||
const title = formatCohortTitle();
|
||||
return [
|
||||
countSubtree("all", title),
|
||||
{
|
||||
name: "New",
|
||||
tree: chartsFromCount({
|
||||
pattern: addrs.new.all,
|
||||
title,
|
||||
metric: "New Addresses",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
...simpleDeltaTree({
|
||||
delta: addrs.delta.all,
|
||||
title,
|
||||
metric: "Address Count",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
activitySubtreeForAll(title),
|
||||
reusedSubtreeForAll(title),
|
||||
exposedSubtree("all", title),
|
||||
];
|
||||
};
|
||||
|
||||
const createAddressSeriesTreeForType =
|
||||
/**
|
||||
* @param {AddressableType} addrType
|
||||
* @param {string} typeName
|
||||
*/
|
||||
(addrType, typeName) => {
|
||||
const title = formatCohortTitle(typeName);
|
||||
return [
|
||||
countSubtree(addrType, title),
|
||||
{
|
||||
name: "New",
|
||||
tree: chartsFromCount({
|
||||
pattern: addrs.new[addrType],
|
||||
title,
|
||||
metric: "New Addresses",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
...simpleDeltaTree({
|
||||
delta: addrs.delta[addrType],
|
||||
title,
|
||||
metric: "Address Count",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
activitySubtreeForType(addrType, title),
|
||||
reusedSubtreeForType(addrType, title),
|
||||
exposedSubtree(addrType, title),
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a "By Type" subtree: Compare (count / tx count / tx %) plus a
|
||||
* per-type drill-down with the same three metrics.
|
||||
@@ -333,11 +552,19 @@ export function createNetworkSection() {
|
||||
* @param {string} args.label - Singular noun for count/tree labels ("Output" / "Prev-Out")
|
||||
* @param {Readonly<Record<K, CountPattern<number>>>} args.count
|
||||
* @param {Readonly<Record<K, CountPattern<number>>>} args.txCount
|
||||
* @param {Readonly<Record<K, PercentRatioCumulativePattern>>} args.txPercent
|
||||
* @param {Readonly<Record<K, PercentRatioCumulativePattern>>} args.share
|
||||
* @param {Readonly<Record<K, PercentRatioCumulativePattern>>} args.txShare
|
||||
* @param {ReadonlyArray<{key: K, name: string, color: Color, defaultActive: boolean}>} args.types
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
const createByTypeTree = ({ label, count, txCount, txPercent, types }) => {
|
||||
const createByTypeTree = ({
|
||||
label,
|
||||
count,
|
||||
share,
|
||||
txCount,
|
||||
txShare,
|
||||
types,
|
||||
}) => {
|
||||
const lowerLabel = label.toLowerCase();
|
||||
return [
|
||||
{
|
||||
@@ -375,7 +602,19 @@ export function createNetworkSection() {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "TX Count",
|
||||
name: "Share",
|
||||
tree: groupedWindowsCumulative({
|
||||
list: types,
|
||||
title: (n) => n,
|
||||
metricTitle: `Share of ${label}s by Type`,
|
||||
getWindowSeries: (t, key) => share[t.key][key].percent,
|
||||
getCumulativeSeries: (t) => share[t.key].percent,
|
||||
seriesFn: line,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Transaction Count",
|
||||
tree: [
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
@@ -406,14 +645,14 @@ export function createNetworkSection() {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "TX Share",
|
||||
name: "Transaction Share",
|
||||
tree: [
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${w.title} Share of Transactions by ${label} Type`,
|
||||
bottom: types.map((t) =>
|
||||
line({
|
||||
series: txPercent[t.key][w.key].percent,
|
||||
series: txShare[t.key][w.key].percent,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.percentage,
|
||||
@@ -426,7 +665,7 @@ export function createNetworkSection() {
|
||||
title: `Cumulative Share of Transactions by ${label} Type`,
|
||||
bottom: types.map((t) =>
|
||||
line({
|
||||
series: txPercent[t.key].percent,
|
||||
series: txShare[t.key].percent,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.percentage,
|
||||
@@ -451,7 +690,15 @@ export function createNetworkSection() {
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "TX Count",
|
||||
name: "Share",
|
||||
tree: chartsFromPercentCumulative({
|
||||
pattern: share[t.key],
|
||||
metric: `Share of ${label}s that are ${t.name}`,
|
||||
color: t.color,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Transaction Count",
|
||||
tree: chartsFromCount({
|
||||
pattern: txCount[t.key],
|
||||
metric: `Transactions with ${t.name} ${lowerLabel}`,
|
||||
@@ -460,9 +707,9 @@ export function createNetworkSection() {
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "TX Share",
|
||||
name: "Transaction Share",
|
||||
tree: chartsFromPercentCumulative({
|
||||
pattern: txPercent[t.key],
|
||||
pattern: txShare[t.key],
|
||||
metric: `Share of Transactions with ${t.name} ${lowerLabel}`,
|
||||
color: t.color,
|
||||
}),
|
||||
@@ -657,20 +904,25 @@ export function createNetworkSection() {
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Weight",
|
||||
tree: chartsFromBlockAnd6b({
|
||||
pattern: transactions.size.weight,
|
||||
metric: "Transaction Weight",
|
||||
unit: Unit.wu,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "vSize",
|
||||
tree: chartsFromBlockAnd6b({
|
||||
pattern: transactions.size.vsize,
|
||||
metric: "Transaction vSize",
|
||||
unit: Unit.vb,
|
||||
}),
|
||||
name: "Size",
|
||||
tree: [
|
||||
{
|
||||
name: "Weight",
|
||||
tree: chartsFromBlockAnd6b({
|
||||
pattern: transactions.size.weight,
|
||||
metric: "Transaction Weight",
|
||||
unit: Unit.wu,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Virtual",
|
||||
tree: chartsFromBlockAnd6b({
|
||||
pattern: transactions.size.vsize,
|
||||
metric: "Transaction vSize",
|
||||
unit: Unit.vb,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Versions",
|
||||
@@ -756,6 +1008,14 @@ export function createNetworkSection() {
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Spendable",
|
||||
tree: chartsFromCount({
|
||||
pattern: outputs.byType.spendableOutputCount,
|
||||
metric: "Spendable Output Count",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Per Second",
|
||||
tree: averagesArray({
|
||||
@@ -769,8 +1029,9 @@ export function createNetworkSection() {
|
||||
tree: createByTypeTree({
|
||||
label: "Output",
|
||||
count: outputs.byType.outputCount,
|
||||
share: outputs.byType.outputShare,
|
||||
txCount: outputs.byType.txCount,
|
||||
txPercent: outputs.byType.txPercent,
|
||||
txShare: outputs.byType.txShare,
|
||||
types: outputTypes,
|
||||
}),
|
||||
},
|
||||
@@ -800,8 +1061,9 @@ export function createNetworkSection() {
|
||||
tree: createByTypeTree({
|
||||
label: "Prev-Out",
|
||||
count: inputs.byType.inputCount,
|
||||
share: inputs.byType.inputShare,
|
||||
txCount: inputs.byType.txCount,
|
||||
txPercent: inputs.byType.txPercent,
|
||||
txShare: inputs.byType.txShare,
|
||||
types: inputTypes,
|
||||
}),
|
||||
},
|
||||
@@ -839,7 +1101,7 @@ export function createNetworkSection() {
|
||||
{
|
||||
name: "Addresses",
|
||||
tree: [
|
||||
...createAddressSeriesTree("all"),
|
||||
...createAddressSeriesTreeForAll(),
|
||||
{
|
||||
name: "By Type",
|
||||
tree: [
|
||||
@@ -861,7 +1123,7 @@ export function createNetworkSection() {
|
||||
},
|
||||
...addressTypes.map((t) => ({
|
||||
name: t.name,
|
||||
tree: createAddressSeriesTree(t.key, t.name),
|
||||
tree: createAddressSeriesTreeForType(t.key, t.name),
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1100,7 +1100,7 @@ export function chartsFromCount({ pattern, title = (s) => s, metric, unit, color
|
||||
}
|
||||
|
||||
/**
|
||||
* Percent + ratio per rolling window + cumulative — mirrors chartsFromCount for percent data.
|
||||
* Percent + ratio per rolling window + cumulative, mirroring chartsFromCount for percent data.
|
||||
* @param {Object} args
|
||||
* @param {PercentRatioCumulativePattern} args.pattern
|
||||
* @param {(metric: string) => string} [args.title]
|
||||
@@ -1153,6 +1153,75 @@ export function chartsFromPercentCumulative({
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* N-pattern variant of {@link chartsFromPercentCumulative}: each chart
|
||||
* overlays one `percentRatio` pair per entry (percent + ratio lines).
|
||||
* Use when comparing alternate views of the same metric (e.g. a share
|
||||
* with two different denominators). Each entry MUST provide a `name`;
|
||||
* if `color` is omitted the entry inherits the per-slot default colors
|
||||
* (window color in Compare/per-window, all-time color for cumulative).
|
||||
* @param {Object} args
|
||||
* @param {ReadonlyArray<{
|
||||
* name: string,
|
||||
* pattern: PercentRatioCumulativePattern,
|
||||
* color?: Color,
|
||||
* }>} args.entries
|
||||
* @param {(metric: string) => string} [args.title]
|
||||
* @param {string} args.metric
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function chartsFromPercentCumulativeEntries({
|
||||
entries,
|
||||
title = (s) => s,
|
||||
metric,
|
||||
}) {
|
||||
return [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title(metric),
|
||||
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
||||
entries.flatMap((e) =>
|
||||
percentRatio({
|
||||
pattern: e.pattern[w.key],
|
||||
name: `${w.name} ${e.name}`,
|
||||
color: e.color ?? w.color,
|
||||
}),
|
||||
),
|
||||
).concat(
|
||||
entries.flatMap((e) =>
|
||||
percentRatio({
|
||||
pattern: e.pattern,
|
||||
name: `All Time ${e.name}`,
|
||||
color: e.color ?? colors.time.all,
|
||||
}),
|
||||
),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`${w.title} ${metric}`),
|
||||
bottom: entries.flatMap((e) =>
|
||||
percentRatio({
|
||||
pattern: e.pattern[w.key],
|
||||
name: e.name,
|
||||
color: e.color ?? w.color,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: title(`Cumulative ${metric}`),
|
||||
bottom: entries.flatMap((e) =>
|
||||
percentRatio({
|
||||
pattern: e.pattern,
|
||||
name: e.name,
|
||||
color: e.color ?? colors.time.all,
|
||||
}),
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Windowed sums + cumulative for multiple named entries (e.g. transaction versions)
|
||||
* @param {Object} args
|
||||
|
||||
@@ -782,23 +782,22 @@ export function createPriceRatioCharts({
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Generic: rolling window charts + cumulative for grouped cohorts
|
||||
* @template {{ name: string, color: Color }} T
|
||||
* @template {{ name: string, color: Color }} A
|
||||
* List-only primitive: rolling window charts + cumulative for a flat
|
||||
* cohort list, no "All" aggregate. Each cohort's `defaultActive` (if
|
||||
* present) is forwarded to `seriesFn`.
|
||||
* @template {{ name: string, color: Color, defaultActive?: boolean }} T
|
||||
* @param {Object} args
|
||||
* @param {readonly T[]} args.list
|
||||
* @param {A} args.all
|
||||
* @param {(name: string) => string} args.title
|
||||
* @param {string} args.metricTitle
|
||||
* @param {(c: T | A, windowKey: "_24h" | "_1w" | "_1m" | "_1y") => AnySeriesPattern} args.getWindowSeries
|
||||
* @param {(c: T | A) => AnySeriesPattern} args.getCumulativeSeries
|
||||
* @param {(args: { series: AnySeriesPattern, name: string, color: Color, unit: Unit }) => AnyFetchedSeriesBlueprint} args.seriesFn
|
||||
* @param {(c: T, windowKey: "_24h" | "_1w" | "_1m" | "_1y") => AnySeriesPattern} args.getWindowSeries
|
||||
* @param {(c: T) => AnySeriesPattern} args.getCumulativeSeries
|
||||
* @param {(args: { series: AnySeriesPattern, name: string, color: Color, unit: Unit, defaultActive?: boolean }) => AnyFetchedSeriesBlueprint} args.seriesFn
|
||||
* @param {Unit} args.unit
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function groupedWindowsCumulative({
|
||||
list,
|
||||
all,
|
||||
title,
|
||||
metricTitle,
|
||||
getWindowSeries,
|
||||
@@ -810,30 +809,70 @@ export function groupedWindowsCumulative({
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`${w.title} ${metricTitle}`),
|
||||
bottom: mapCohortsWithAll(list, all, (c) =>
|
||||
bottom: list.map((c) =>
|
||||
seriesFn({
|
||||
series: getWindowSeries(c, w.key),
|
||||
name: c.name,
|
||||
color: c.color,
|
||||
unit,
|
||||
defaultActive: c.defaultActive,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: title(`Cumulative ${metricTitle}`),
|
||||
bottom: mapCohortsWithAll(list, all, (c) =>
|
||||
bottom: list.map((c) =>
|
||||
seriesFn({
|
||||
series: getCumulativeSeries(c),
|
||||
name: c.name,
|
||||
color: c.color,
|
||||
unit,
|
||||
defaultActive: c.defaultActive,
|
||||
}),
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* "With all" variant: same as {@link groupedWindowsCumulative} plus an
|
||||
* "All" aggregate appended to each chart with `defaultActive: false`.
|
||||
* Composes on top of the primitive.
|
||||
* @template {{ name: string, color: Color }} T
|
||||
* @template {{ name: string, color: Color }} A
|
||||
* @param {Object} args
|
||||
* @param {readonly T[]} args.list
|
||||
* @param {A} args.all
|
||||
* @param {(name: string) => string} args.title
|
||||
* @param {string} args.metricTitle
|
||||
* @param {(c: T | A, windowKey: "_24h" | "_1w" | "_1m" | "_1y") => AnySeriesPattern} args.getWindowSeries
|
||||
* @param {(c: T | A) => AnySeriesPattern} args.getCumulativeSeries
|
||||
* @param {(args: { series: AnySeriesPattern, name: string, color: Color, unit: Unit, defaultActive?: boolean }) => AnyFetchedSeriesBlueprint} args.seriesFn
|
||||
* @param {Unit} args.unit
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function groupedWindowsCumulativeWithAll({
|
||||
list,
|
||||
all,
|
||||
title,
|
||||
metricTitle,
|
||||
getWindowSeries,
|
||||
getCumulativeSeries,
|
||||
seriesFn,
|
||||
unit,
|
||||
}) {
|
||||
return groupedWindowsCumulative({
|
||||
list: [...list, { ...all, name: "All", defaultActive: false }],
|
||||
title,
|
||||
metricTitle,
|
||||
getWindowSeries,
|
||||
getCumulativeSeries,
|
||||
seriesFn,
|
||||
unit,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* USD variant: windows access .sum[key].usd, cumulative accesses .cumulative.usd
|
||||
* @template {{ name: string, color: Color }} T
|
||||
@@ -855,7 +894,7 @@ export function groupedWindowsCumulativeUsd({
|
||||
getMetric,
|
||||
seriesFn = line,
|
||||
}) {
|
||||
return groupedWindowsCumulative({
|
||||
return groupedWindowsCumulativeWithAll({
|
||||
list,
|
||||
all,
|
||||
title,
|
||||
|
||||
Reference in New Issue
Block a user