global: snap

This commit is contained in:
nym21
2026-04-15 12:51:30 +02:00
parent 39da441d14
commit 08ba4ad996
24 changed files with 1076 additions and 620 deletions

View File

@@ -353,6 +353,17 @@ export function createCointimeSection() {
{
name: "Indicators",
tree: [
{
name: "AVIV",
title: "AVIV Ratio",
bottom: [
line({
series: cap.aviv.ratio,
name: "AVIV",
unit: Unit.ratio,
}),
],
},
{
name: "Reserve Risk",
title: "Reserve Risk",
@@ -365,22 +376,11 @@ export function createCointimeSection() {
}),
],
},
{
name: "AVIV",
title: "AVIV Ratio",
bottom: [
line({
series: cap.aviv.ratio,
name: "AVIV",
unit: Unit.ratio,
}),
],
},
],
},
{
name: "Cointime-Adjusted",
name: "Adjusted",
tree: [
{
name: "Inflation",

View File

@@ -4,14 +4,14 @@ import { brk } from "../../utils/client.js";
/** @type {readonly AddressableType[]} */
const ADDRESSABLE_TYPES = [
"p2pk65",
"p2pk33",
"p2pkh",
"p2sh",
"p2wpkh",
"p2wsh",
"p2tr",
"p2a",
"p2tr",
"p2wsh",
"p2wpkh",
"p2sh",
"p2pkh",
"p2pk33",
"p2pk65",
];
/** @type {(key: SpendableType) => key is AddressableType} */
@@ -162,12 +162,13 @@ export function buildCohortData() {
},
);
const typeAddressable = ADDRESSABLE_TYPES.map((key, i, arr) => {
const typeAddressable = ADDRESSABLE_TYPES.map((key) => {
const names = SPENDABLE_TYPE_NAMES[key];
return {
key,
name: names.short,
title: names.short,
color: colors.at(i, arr.length),
color: colors.scriptType[key],
tree: utxoCohorts.type[key],
addressCount: {
base: addrs.funded[key],
@@ -178,10 +179,11 @@ export function buildCohortData() {
const typeOther = entries(SPENDABLE_TYPE_NAMES)
.filter(([key]) => !isAddressable(key))
.map(([key, names], i, arr) => ({
.map(([key, names]) => ({
key,
name: names.short,
title: names.short,
color: colors.at(i, arr.length),
color: colors.scriptType[key],
tree: utxoCohorts.type[key],
}));

View File

@@ -25,6 +25,7 @@ import {
} from "../series.js";
import { Unit } from "../../utils/units.js";
import { colors } from "../../utils/colors.js";
import { brk } from "../../utils/client.js";
// Section builders
import {
@@ -743,3 +744,19 @@ export function createUtxoProfitabilitySection({ range, profit, loss }) {
],
};
}
/**
* Gini leaf for Distribution > Address Balance
* @returns {AnyPartialOption}
*/
export function createAddressBalanceGiniLeaf() {
return {
name: "Gini",
title: "Address Balance Gini Coefficient",
bottom: percentRatio({
pattern: brk.series.indicators.gini,
name: "Gini",
color: colors.loss,
}),
};
}

View File

@@ -653,120 +653,117 @@ export function createMarketSection() {
],
},
// Momentum
// RSI
{
name: "Momentum",
name: "RSI",
tree: [
{
name: "RSI",
tree: [
{
name: "Compare",
title: "RSI",
bottom: [
...ROLLING_WINDOWS_TO_1M.flatMap((w) =>
indexRatio({
pattern: technical.rsi[w.key].rsi,
name: w.name,
color: w.color,
}),
),
priceLine({ unit: Unit.index, number: 70 }),
priceLine({ unit: Unit.index, number: 30 }),
],
},
...ROLLING_WINDOWS_TO_1M.map((w) => {
const rsi = technical.rsi[w.key];
return {
name: "Compare",
title: "RSI",
bottom: [
...ROLLING_WINDOWS_TO_1M.flatMap((w) =>
indexRatio({
pattern: technical.rsi[w.key].rsi,
name: w.name,
title: `${w.title} RSI`,
bottom: [
...indexRatio({
pattern: rsi.rsi,
name: "RSI",
color: colors.indicator.main,
}),
priceLine({ unit: Unit.index, number: 70 }),
priceLine({
unit: Unit.index,
number: 50,
defaultActive: false,
}),
priceLine({ unit: Unit.index, number: 30 }),
],
};
}),
{
name: "Stochastic",
tree: ROLLING_WINDOWS_TO_1M.map((w) => {
const rsi = technical.rsi[w.key];
return {
name: w.name,
title: `${w.title} Stochastic RSI`,
bottom: [
...indexRatio({
pattern: rsi.stochRsiK,
name: "K",
color: colors.indicator.fast,
}),
...indexRatio({
pattern: rsi.stochRsiD,
name: "D",
color: colors.indicator.slow,
}),
...priceLines({
unit: Unit.index,
numbers: [80, 20],
}),
],
};
color: w.color,
}),
},
),
priceLine({ unit: Unit.index, number: 70 }),
priceLine({ unit: Unit.index, number: 30 }),
],
},
...ROLLING_WINDOWS_TO_1M.map((w) => {
const rsi = technical.rsi[w.key];
return {
name: w.name,
title: `${w.title} RSI`,
bottom: [
...indexRatio({
pattern: rsi.rsi,
name: "RSI",
color: colors.indicator.main,
}),
priceLine({ unit: Unit.index, number: 70 }),
priceLine({
unit: Unit.index,
number: 50,
defaultActive: false,
}),
priceLine({ unit: Unit.index, number: 30 }),
],
};
}),
{
name: "MACD",
tree: [
{
name: "Compare",
title: "MACD",
bottom: ROLLING_WINDOWS_TO_1M.map((w) =>
line({
series: technical.macd[w.key].line,
name: w.name,
color: w.color,
unit: Unit.usd,
}),
),
},
...ROLLING_WINDOWS_TO_1M.map((w) => ({
name: "Stochastic",
tree: ROLLING_WINDOWS_TO_1M.map((w) => {
const rsi = technical.rsi[w.key];
return {
name: w.name,
title: `${w.title} MACD`,
title: `${w.title} Stochastic RSI`,
bottom: [
line({
series: technical.macd[w.key].line,
name: "MACD",
...indexRatio({
pattern: rsi.stochRsiK,
name: "K",
color: colors.indicator.fast,
unit: Unit.usd,
}),
line({
series: technical.macd[w.key].signal,
name: "Signal",
...indexRatio({
pattern: rsi.stochRsiD,
name: "D",
color: colors.indicator.slow,
unit: Unit.usd,
}),
histogram({
series: technical.macd[w.key].histogram,
name: "Histogram",
unit: Unit.usd,
...priceLines({
unit: Unit.index,
numbers: [80, 20],
}),
],
})),
],
};
}),
},
],
},
// MACD
{
name: "MACD",
tree: [
{
name: "Compare",
title: "MACD",
bottom: ROLLING_WINDOWS_TO_1M.map((w) =>
line({
series: technical.macd[w.key].line,
name: w.name,
color: w.color,
unit: Unit.usd,
}),
),
},
...ROLLING_WINDOWS_TO_1M.map((w) => ({
name: w.name,
title: `${w.title} MACD`,
bottom: [
line({
series: technical.macd[w.key].line,
name: "MACD",
color: colors.indicator.fast,
unit: Unit.usd,
}),
line({
series: technical.macd[w.key].signal,
name: "Signal",
color: colors.indicator.slow,
unit: Unit.usd,
}),
histogram({
series: technical.macd[w.key].histogram,
name: "Histogram",
unit: Unit.usd,
}),
],
})),
],
},
// Volatility
{
name: "Volatility",
@@ -925,201 +922,190 @@ export function createMarketSection() {
name: "Indicators",
tree: [
{
name: "Envelope",
title: "Realized Envelope",
top: priceBands(percentileBands(indicators.realizedEnvelope), {
defaultActive: true,
name: "Rarity Meter",
tree: /** @type {const} */ ([
{ key: "full", name: "Full", title: "Rarity Meter" },
{ key: "local", name: "Local", title: "Local Rarity Meter" },
{ key: "cycle", name: "Cycle", title: "Cycle Rarity Meter" },
]).map((v) => {
const m = indicators.rarityMeter[v.key];
return {
name: v.name,
title: v.title,
top: priceBands(percentileBands(m), { defaultActive: true }),
bottom: [
histogram({
series: m.index,
name: "Index",
unit: Unit.count,
colorFn: (v) =>
/** @type {const} */ ([
colors.ratioPct._0_5,
colors.ratioPct._1,
colors.ratioPct._2,
colors.ratioPct._5,
colors.transparent,
colors.ratioPct._95,
colors.ratioPct._98,
colors.ratioPct._99,
colors.ratioPct._99_5,
])[v + 4],
}),
baseline({
series: m.score,
name: "Score",
unit: Unit.count,
color: [colors.ratioPct._99, colors.ratioPct._1],
defaultActive: false,
}),
],
};
}),
},
{
name: "NVT",
title: "NVT Ratio",
bottom: [
histogram({
series: indicators.realizedEnvelope.index,
name: "Index",
unit: Unit.count,
colorFn: (v) =>
/** @type {const} */ ([
colors.ratioPct._0_5,
colors.ratioPct._1,
colors.ratioPct._2,
colors.ratioPct._5,
colors.transparent,
colors.ratioPct._95,
colors.ratioPct._98,
colors.ratioPct._99,
colors.ratioPct._99_5,
])[v + 4],
line({
series: indicators.nvt.ratio,
name: "NVT",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
{
name: "Thermocap Multiple",
title: "Thermocap Multiple",
bottom: [
line({
series: indicators.thermoCapMultiple.ratio,
name: "Thermocap",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
{
name: "Puell Multiple",
title: "Puell Multiple",
bottom: [
line({
series: indicators.puellMultiple.ratio,
name: "Puell",
color: colors.usd,
unit: Unit.ratio,
}),
],
},
{
name: "RHODL Ratio",
title: "RHODL Ratio",
bottom: [
line({
series: indicators.rhodlRatio.ratio,
name: "RHODL",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
{
name: "Stock-to-Flow",
title: "Stock-to-Flow",
bottom: [
line({
series: indicators.stockToFlow,
name: "S2F",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
{
name: "Pi Cycle",
title: "Pi Cycle",
top: [
price({
series: ma.sma._111d,
name: "111d SMA",
color: colors.indicator.upper,
}),
price({
series: ma.sma._350d.x2,
name: "350d SMA x2",
color: colors.indicator.lower,
}),
],
bottom: [
baseline({
series: indicators.realizedEnvelope.score,
name: "Score",
unit: Unit.count,
color: [colors.ratioPct._99, colors.ratioPct._1],
series: technical.piCycle.ratio,
name: "Pi Cycle",
unit: Unit.ratio,
base: 1,
}),
],
},
{
name: "Dormancy",
title: "Dormancy",
bottom: [
line({
series: indicators.dormancy.supplyAdj,
name: "Supply Adjusted",
color: colors.bitcoin,
unit: Unit.ratio,
}),
line({
series: indicators.dormancy.flow,
name: "Flow",
color: colors.usd,
unit: Unit.ratio,
defaultActive: false,
}),
],
},
{
name: "Valuation",
tree: [
{
name: "NVT",
title: "NVT Ratio",
bottom: [
line({
series: indicators.nvt.ratio,
name: "NVT",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
{
name: "Thermocap Multiple",
title: "Thermocap Multiple",
bottom: [
line({
series: indicators.thermoCapMultiple.ratio,
name: "Thermocap",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
name: "Seller Exhaustion",
title: "Seller Exhaustion Constant",
bottom: [
line({
series: indicators.sellerExhaustion,
name: "SEC",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
{
name: "Cycle",
name: "Coin Destruction",
tree: [
{
name: "Pi Cycle",
title: "Pi Cycle",
top: [
price({
series: ma.sma._111d,
name: "111d SMA",
color: colors.indicator.upper,
}),
price({
series: ma.sma._350d.x2,
name: "350d SMA x2",
color: colors.indicator.lower,
}),
],
bottom: [
baseline({
series: technical.piCycle.ratio,
name: "Pi Cycle",
unit: Unit.ratio,
base: 1,
}),
],
},
{
name: "Stock-to-Flow",
title: "Stock-to-Flow",
bottom: [
line({
series: indicators.stockToFlow,
name: "S2F",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
{
name: "Puell Multiple",
title: "Puell Multiple",
bottom: [
line({
series: indicators.puellMultiple.ratio,
name: "Puell",
color: colors.usd,
unit: Unit.ratio,
}),
],
},
{
name: "RHODL Ratio",
title: "RHODL Ratio",
bottom: [
line({
series: indicators.rhodlRatio.ratio,
name: "RHODL",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
],
},
{
name: "Activity",
tree: [
{
name: "Dormancy",
title: "Dormancy",
bottom: [
line({
series: indicators.dormancy.supplyAdj,
name: "Supply Adjusted",
color: colors.bitcoin,
unit: Unit.ratio,
}),
line({
series: indicators.dormancy.flow,
name: "Flow",
color: colors.usd,
unit: Unit.ratio,
defaultActive: false,
}),
],
},
{
name: "Seller Exhaustion",
title: "Seller Exhaustion Constant",
bottom: [
line({
series: indicators.sellerExhaustion,
name: "SEC",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
{
name: "CDD Supply Adjusted",
name: "CDD",
title: "Coindays Destroyed (Supply Adjusted)",
bottom: [
line({
series: indicators.coindaysDestroyedSupplyAdj,
name: "CDD SA",
name: "CDD",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
{
name: "CYD Supply Adjusted",
name: "CYD",
title: "Coinyears Destroyed (Supply Adjusted)",
bottom: [
line({
series: indicators.coinyearsDestroyedSupplyAdj,
name: "CYD SA",
color: colors.bitcoin,
name: "CYD",
color: colors.usd,
unit: Unit.ratio,
}),
],
},
],
},
{
name: "Gini",
title: "Gini Coefficient",
bottom: percentRatio({
pattern: indicators.gini,
name: "Gini",
color: colors.loss,
}),
},
],
},
],

View File

@@ -190,9 +190,10 @@ export function createMiningSection() {
/**
* @param {string} groupTitle
* @param {typeof majorPoolData} poolList
* @param {string} [name]
*/
const createPoolCompare = (groupTitle, poolList) => ({
name: "Compare",
const createPoolCompare = (groupTitle, poolList, name = "Compare") => ({
name,
tree: [
{
name: "Dominance",
@@ -602,7 +603,7 @@ export function createMiningSection() {
{
name: "Pools",
tree: [
createPoolCompare("Major Pools", featuredPools),
createPoolCompare("Major Pools", featuredPools, "Featured"),
{
name: "AntPool & Friends",
tree: [

View File

@@ -6,6 +6,7 @@ import { Unit } from "../utils/units.js";
import { entries } from "../utils/array.js";
import {
line,
baseline,
fromSupplyPattern,
chartsFromFull,
chartsFromFullPerBlock,
@@ -14,6 +15,7 @@ import {
chartsFromPercentCumulative,
chartsFromPercentCumulativeEntries,
chartsFromAggregatedPerBlock,
distributionWindowsTree,
averagesArray,
simpleDeltaTree,
ROLLING_WINDOWS,
@@ -91,39 +93,6 @@ export function createNetworkSection() {
{ key: "reactivated", name: "Reactivated" },
]);
const countTypes = /** @type {const} */ ([
{
name: "Funded",
title: "Address Count by Type",
/** @param {AddressableType} t */
getSeries: (t) => addrs.funded[t],
},
{
name: "Empty",
title: "Empty Address Count by Type",
/** @param {AddressableType} t */
getSeries: (t) => addrs.empty[t],
},
{
name: "Total",
title: "Total Address Count by Type",
/** @param {AddressableType} t */
getSeries: (t) => addrs.total[t],
},
{
name: "Funded Reused",
title: "Funded Reused Address Count by Type",
/** @param {AddressableType} t */
getSeries: (t) => addrs.reused.count.funded[t],
},
{
name: "Total Reused",
title: "Total Reused Address Count by Type",
/** @param {AddressableType} t */
getSeries: (t) => addrs.reused.count.total[t],
},
]);
const countMetrics = /** @type {const} */ ([
{ key: "funded", name: "Funded", color: undefined },
{ key: "empty", name: "Empty", color: colors.gray },
@@ -543,6 +512,261 @@ export function createNetworkSection() {
];
};
/**
* Mirror of the per-type singles tree, but every leaf is a cross-type
* comparison chart (same metric, same unit, one line per addr type).
* Structure parallels `createAddressSeriesTreeForType` section-by-section
* so users can compare anything they can view on a single type.
*/
const createAddressByTypeCompare = () => {
const typeLines =
/**
* @param {(t: (typeof addressTypes)[number]) => AnySeriesPattern} getSeries
* @param {Unit} [unit]
*/
(getSeries, unit = Unit.count) =>
addressTypes.map((t) =>
line({
series: getSeries(t),
name: t.name,
color: t.color,
unit,
defaultActive: t.defaultActive,
}),
);
const typeBaselines =
/**
* @param {(t: (typeof addressTypes)[number]) => AnySeriesPattern} getSeries
* @param {Unit} [unit]
*/
(getSeries, unit = Unit.count) =>
addressTypes.map((t) =>
baseline({
series: getSeries(t),
name: t.name,
color: t.color,
unit,
defaultActive: t.defaultActive,
}),
);
return {
name: "Compare",
tree: [
// Count (lifetime Funded/Empty/Total)
{
name: "Count",
tree: countMetrics.map((m) => ({
name: m.name,
title: `${m.name} Address Count by Type`,
bottom: typeLines((t) => addrs[m.key][t.key]),
})),
},
// New (rolling sums + cumulative)
{
name: "New",
tree: groupedWindowsCumulative({
list: addressTypes,
title: (s) => s,
metricTitle: "New Addresses by Type",
getWindowSeries: (t, key) => addrs.new[t.key].sum[key],
getCumulativeSeries: (t) => addrs.new[t.key].cumulative,
seriesFn: line,
unit: Unit.count,
}),
},
// Change (rolling deltas, signed, baseline)
{
name: "Change",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `${w.title} Address Count Change by Type`,
bottom: typeBaselines(
(t) => addrs.delta[t.key].absolute[w.key],
),
})),
},
// Growth Rate (rolling percent rates)
{
name: "Growth Rate",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `${w.title} Address Growth Rate by Type`,
bottom: typeLines(
(t) => addrs.delta[t.key].rate[w.key].percent,
Unit.percentage,
),
})),
},
// Activity (per activity type, per window)
{
name: "Activity",
tree: activityTypes.map((a) => ({
name: a.name,
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `${w.title} ${a.name} Addresses by Type`,
bottom: typeLines(
(t) => addrs.activity[t.key][a.key][w.key],
),
})),
})),
},
// Reused
{
name: "Reused",
tree: [
{
name: "Funded",
title: "Funded Reused Address Count by Type",
bottom: typeLines((t) => addrs.reused.count.funded[t.key]),
},
{
name: "Total",
title: "Total Reused Address Count by Type",
bottom: typeLines((t) => addrs.reused.count.total[t.key]),
},
{
name: "Outputs",
tree: [
{
name: "Count",
tree: groupedWindowsCumulative({
list: addressTypes,
title: (s) => s,
metricTitle:
"Transaction Outputs to Reused Addresses by Type",
getWindowSeries: (t, key) =>
addrs.reused.events.outputToReusedAddrCount[t.key].sum[
key
],
getCumulativeSeries: (t) =>
addrs.reused.events.outputToReusedAddrCount[t.key]
.cumulative,
seriesFn: line,
unit: Unit.count,
}),
},
{
name: "Share",
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `${w.title} Share of Transaction Outputs to Reused Addresses by Type`,
bottom: typeLines(
(t) =>
addrs.reused.events.outputToReusedAddrShare[t.key][
w.key
].percent,
Unit.percentage,
),
})),
{
name: "Cumulative",
title:
"Cumulative Share of Transaction Outputs to Reused Addresses by Type",
bottom: typeLines(
(t) =>
addrs.reused.events.outputToReusedAddrShare[t.key]
.percent,
Unit.percentage,
),
},
],
},
],
},
{
name: "Inputs",
tree: [
{
name: "Count",
tree: groupedWindowsCumulative({
list: addressTypes,
title: (s) => s,
metricTitle:
"Transaction Inputs from Reused Addresses by Type",
getWindowSeries: (t, key) =>
addrs.reused.events.inputFromReusedAddrCount[t.key].sum[
key
],
getCumulativeSeries: (t) =>
addrs.reused.events.inputFromReusedAddrCount[t.key]
.cumulative,
seriesFn: line,
unit: Unit.count,
}),
},
{
name: "Share",
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `${w.title} Share of Transaction Inputs from Reused Addresses by Type`,
bottom: typeLines(
(t) =>
addrs.reused.events.inputFromReusedAddrShare[t.key][
w.key
].percent,
Unit.percentage,
),
})),
{
name: "Cumulative",
title:
"Cumulative Share of Transaction Inputs from Reused Addresses by Type",
bottom: typeLines(
(t) =>
addrs.reused.events.inputFromReusedAddrShare[t.key]
.percent,
Unit.percentage,
),
},
],
},
],
},
],
},
// Exposed
{
name: "Exposed",
tree: [
{
name: "Funded",
title: "Funded Exposed Address Count by Type",
bottom: typeLines((t) => addrs.exposed.count.funded[t.key]),
},
{
name: "Total",
title: "Total Exposed Address Count by Type",
bottom: typeLines((t) => addrs.exposed.count.total[t.key]),
},
{
name: "Supply",
title: "Supply in Exposed Addresses by Type",
bottom: addressTypes.flatMap((t) =>
satsBtcUsd({
pattern: addrs.exposed.supply[t.key],
name: t.name,
color: t.color,
defaultActive: t.defaultActive,
}),
),
},
],
},
],
};
};
/**
* Build a "By Type" subtree: Compare (count / tx count / tx %) plus a
* per-type drill-down with the same three metrics.
@@ -754,7 +978,7 @@ export function createNetworkSection() {
name: "Unspendable",
tree: [
{
name: "Total",
name: "All",
title: "Unspendable Supply",
bottom: satsBtcUsdFrom({
source: supply.burned,
@@ -1002,19 +1226,74 @@ export function createNetworkSection() {
tree: [
{
name: "Count",
tree: chartsFromAggregatedPerBlock({
pattern: outputs.count.total,
metric: "Output Count",
unit: Unit.count,
}),
},
{
name: "Spendable",
tree: chartsFromCount({
pattern: outputs.byType.spendableOutputCount,
metric: "Spendable Output Count",
unit: Unit.count,
}),
tree: [
{
name: "Compare",
title: "Output Count",
bottom: ROLLING_WINDOWS.map((w) =>
line({
series: outputs.count.total.rolling.average[w.key],
name: w.name,
color: w.color,
unit: Unit.count,
}),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `${w.title} Output Count`,
bottom: [
line({
series: outputs.count.total.rolling.sum[w.key],
name: "Total (Sum)",
color: w.color,
unit: Unit.count,
}),
line({
series: outputs.byType.spendableOutputCount.sum[w.key],
name: "Spendable (Sum)",
color: colors.gray,
unit: Unit.count,
}),
line({
series: outputs.count.total.rolling.average[w.key],
name: "Total (Avg)",
color: w.color,
unit: Unit.count,
defaultActive: false,
}),
line({
series: outputs.byType.spendableOutputCount.average[w.key],
name: "Spendable (Avg)",
color: colors.gray,
unit: Unit.count,
defaultActive: false,
}),
],
})),
{
name: "Cumulative",
title: "Cumulative Output Count",
bottom: [
line({
series: outputs.count.total.cumulative,
name: "Total",
unit: Unit.count,
}),
line({
series: outputs.byType.spendableOutputCount.cumulative,
name: "Spendable",
color: colors.gray,
unit: Unit.count,
}),
],
},
distributionWindowsTree({
pattern: outputs.count.total.rolling,
metric: "Output Count per Block",
unit: Unit.count,
}),
],
},
{
name: "Per Second",
@@ -1105,22 +1384,7 @@ export function createNetworkSection() {
{
name: "By Type",
tree: [
{
name: "Compare",
tree: countTypes.map((c) => ({
name: c.name,
title: c.title,
bottom: addressTypes.map((t) =>
line({
series: c.getSeries(t.key),
name: t.name,
color: t.color,
unit: Unit.count,
defaultActive: t.defaultActive,
}),
),
})),
},
createAddressByTypeCompare(),
...addressTypes.map((t) => ({
name: t.name,
tree: createAddressSeriesTreeForType(t.key, t.name),

View File

@@ -18,6 +18,7 @@ import {
createGroupedCohortFolderAddress,
createGroupedAddressCohortFolder,
createUtxoProfitabilitySection,
createAddressBalanceGiniLeaf,
} from "./distribution/index.js";
import { createMarketSection } from "./market.js";
import { createNetworkSection } from "./network.js";
@@ -91,7 +92,7 @@ export function createPartialOptions() {
name: "UTXO Age",
tree: [
{
name: "Younger Than",
name: "Under",
tree: [
createGroupedCohortFolderWithAdjusted({
name: "Compare",
@@ -103,7 +104,7 @@ export function createPartialOptions() {
],
},
{
name: "Older Than",
name: "Over",
tree: [
createGroupedCohortFolderWithAdjusted({
name: "Compare",
@@ -133,7 +134,7 @@ export function createPartialOptions() {
name: "UTXO Size",
tree: [
{
name: "Less Than",
name: "Under",
tree: [
createGroupedCohortFolderBasicWithMarketCap({
name: "Compare",
@@ -147,7 +148,7 @@ export function createPartialOptions() {
],
},
{
name: "More Than",
name: "Over",
tree: [
createGroupedCohortFolderBasicWithMarketCap({
name: "Compare",
@@ -187,7 +188,7 @@ export function createPartialOptions() {
name: "Address Balance",
tree: [
{
name: "Less Than",
name: "Under",
tree: [
createGroupedAddressCohortFolder({
name: "Compare",
@@ -199,7 +200,7 @@ export function createPartialOptions() {
],
},
{
name: "More Than",
name: "Over",
tree: [
createGroupedAddressCohortFolder({
name: "Compare",
@@ -222,6 +223,7 @@ export function createPartialOptions() {
...addressesAmountRange.map(createAddressCohortFolder),
],
},
createAddressBalanceGiniLeaf(),
],
},
@@ -234,8 +236,25 @@ export function createPartialOptions() {
list: typeAddressable,
all: cohortAll,
}),
...typeAddressable.map(createCohortFolderAddress),
...typeOther.map(createCohortFolderWithoutRelative),
.../** @satisfies {readonly SpendableType[]} */ ([
"p2a",
"p2tr",
"p2wsh",
"p2wpkh",
"p2sh",
"p2ms",
"p2pkh",
"p2pk33",
"p2pk65",
"empty",
"unknown",
]).flatMap((key) => {
const addr = typeAddressable.find((t) => t.key === key);
if (addr) return [createCohortFolderAddress(addr)];
const other = typeOther.find((t) => t.key === key);
if (other) return [createCohortFolderWithoutRelative(other)];
return [];
}),
],
},