diff --git a/websites/default/packages/lightweight-charts/wrapper.js b/websites/default/packages/lightweight-charts/wrapper.js index ec81de792..98f0564b2 100644 --- a/websites/default/packages/lightweight-charts/wrapper.js +++ b/websites/default/packages/lightweight-charts/wrapper.js @@ -988,18 +988,21 @@ function createPriceLine(series, options, colors) { }); } -/** @param {number} value */ -function numberToShortUSFormat(value) { +/** + * @param {number} value + * @param {0 | 2} [digits] + */ +function numberToShortUSFormat(value, digits) { const absoluteValue = Math.abs(value); if (isNaN(value)) { return ""; } else if (absoluteValue < 10) { - return numberToUSFormat(value, 3); + return numberToUSFormat(value, Math.min(3, digits || 10)); } else if (absoluteValue < 1_000) { - return numberToUSFormat(value, 2); + return numberToUSFormat(value, Math.min(2, digits || 10)); } else if (absoluteValue < 10_000) { - return numberToUSFormat(value, 1); + return numberToUSFormat(value, Math.min(1, digits || 10)); } else if (absoluteValue < 1_000_000) { return numberToUSFormat(value, 0); } else if (absoluteValue >= 900_000_000_000_000_000) { diff --git a/websites/default/scripts/chart.js b/websites/default/scripts/chart.js index 85f62287b..53a5d8721 100644 --- a/websites/default/scripts/chart.js +++ b/websites/default/scripts/chart.js @@ -10,7 +10,7 @@ const CANDLE = "candle"; * @param {Object} args * @param {Colors} args.colors * @param {LightweightCharts} args.lightweightCharts - * @param {Accessor} args.selected + * @param {Accessor} args.option * @param {Signals} args.signals * @param {Utilities} args.utils * @param {WebSockets} args.webSockets @@ -22,7 +22,7 @@ export function init({ colors, elements, lightweightCharts, - selected, + option, signals, utils, webSockets, @@ -35,7 +35,12 @@ export function init({ const { headerElement, headingElement } = utils.dom.createHeader(); elements.charts.append(headerElement); - const { index, fieldset } = createIndexSelector({ signals, utils }); + const { index, fieldset } = createIndexSelector({ + option, + vecIdToIndexes, + signals, + utils, + }); const TIMERANGE_LS_KEY = signals.createMemo( () => `chart-timerange-${index()}` @@ -231,7 +236,7 @@ export function init({ } } - signals.createEffect(selected, (option) => { + signals.createEffect(option, (option) => { headingElement.innerHTML = option.title; const bottomUnits = /** @type {readonly Unit[]} */ ( @@ -449,25 +454,53 @@ export function init({ /** * @param {Object} args + * @param {Accessor} args.option + * @param {VecIdToIndexes} args.vecIdToIndexes * @param {Signals} args.signals * @param {Utilities} args.utils */ -function createIndexSelector({ signals, utils }) { +function createIndexSelector({ option, vecIdToIndexes, signals, utils }) { + const choices_ = /** @type {const} */ ([ + "timestamp", + "date", + "week", + // "difficulty epoch", + "month", + "quarter", + "year", + // "halving epoch", + "decade", + ]); + + /** @type {Accessor} */ + const choices = signals.createMemo(() => { + const o = option(); + + if (!Object.keys(o.top).length && !Object.keys(o.bottom).length) { + return [...choices_]; + } + const rawIndexes = new Set( + [Object.values(o.top), Object.values(o.bottom)] + .flat(2) + .map((blueprint) => vecIdToIndexes[blueprint.key]) + .flat() + ); + + const serializedIndexes = [...rawIndexes].flatMap((index) => { + const c = utils.serde.chartableIndex.serialize(index); + return c ? [c] : []; + }); + + return /** @type {any} */ ( + choices_.filter((choice) => serializedIndexes.includes(choice)) + ); + }); + const { field, selected } = utils.dom.createHorizontalChoiceField({ defaultValue: "date", keyPrefix, key: "index", - choices: /**@type {const} */ ([ - "timestamp", - "date", - "week", - // "difficulty epoch", - "month", - "quarter", - "year", - // "halving epoch", - "decade", - ]), + choices, id: "index", signals, }); @@ -476,25 +509,8 @@ function createIndexSelector({ signals, utils }) { fieldset.append(field); fieldset.dataset.size = "sm"; - const index = signals.createMemo( - /** @returns {ChartableIndex} */ () => { - switch (selected()) { - case "timestamp": - return /** @satisfies {Height} */ (5); - case "date": - return /** @satisfies {DateIndex} */ (0); - case "week": - return /** @satisfies {WeekIndex} */ (22); - case "month": - return /** @satisfies {MonthIndex} */ (7); - case "quarter": - return /** @satisfies {QuarterIndex} */ (19); - case "year": - return /** @satisfies {YearIndex} */ (23); - case "decade": - return /** @satisfies {DecadeIndex} */ (1); - } - } + const index = signals.createMemo(() => + utils.serde.chartableIndex.deserialize(selected()) ); return { fieldset, index }; diff --git a/websites/default/scripts/main.js b/websites/default/scripts/main.js index 9005836e0..9a8dd2a6c 100644 --- a/websites/default/scripts/main.js +++ b/websites/default/scripts/main.js @@ -362,11 +362,11 @@ function createUtils() { * @param {Object} args * @param {T[number]} args.defaultValue * @param {string} [args.id] - * @param {T} args.choices + * @param {T | Accessor} args.choices * @param {string} [args.keyPrefix] * @param {string} args.key * @param {boolean} [args.sorted] - * @param {{createEffect: CreateEffect, createSignal: Signals["createSignal"]}} args.signals + * @param {{createEffect: CreateEffect, createMemo: CreateMemo, createSignal: Signals["createSignal"]}} args.signals */ createHorizontalChoiceField({ id, @@ -377,13 +377,21 @@ function createUtils() { signals, sorted, }) { - const choices = sorted - ? /** @type {T} */ ( - /** @type {any} */ ( - unsortedChoices.toSorted((a, b) => a.localeCompare(b)) + const choices = signals.createMemo(() => { + /** @type {T} */ + let c; + if (typeof unsortedChoices === "function") { + c = unsortedChoices(); + } else { + c = unsortedChoices; + } + + return sorted + ? /** @type {T} */ ( + /** @type {any} */ (c.toSorted((a, b) => a.localeCompare(b))) ) - ) - : unsortedChoices; + : c; + }); /** @type {Signal} */ const selected = signals.createSignal(defaultValue, { @@ -394,31 +402,39 @@ function createUtils() { }, }); - if (!choices.includes(selected())) { - console.log(choices, "don't include", selected()); - selected.set(() => defaultValue); - } - const field = window.document.createElement("div"); field.classList.add("field"); const div = window.document.createElement("div"); field.append(div); - choices.forEach((choice) => { - const inputValue = choice; - const { label } = this.createLabeledInput({ - inputId: `${id ?? key}-${choice.toLowerCase()}`, - inputName: id ?? key, - inputValue, - inputChecked: inputValue === selected(), - labelTitle: choice, - type: "radio", - }); + signals.createEffect(choices, (choices) => { + const s = selected(); + if (!choices.includes(s)) { + if (choices.includes(defaultValue)) { + selected.set(() => defaultValue); + } else if (choices.length) { + selected.set(() => choices[0]); + } + } - const text = window.document.createTextNode(choice); - label.append(text); - div.append(label); + div.innerHTML = ""; + + choices.forEach((choice) => { + const inputValue = choice; + const { label } = this.createLabeledInput({ + inputId: `${id ?? key}-${choice.toLowerCase()}`, + inputName: id ?? key, + inputValue, + inputChecked: inputValue === selected(), + labelTitle: choice, + type: "radio", + }); + + const text = window.document.createTextNode(choice); + label.append(text); + div.append(label); + }); }); field.addEventListener("change", (event) => { @@ -1093,6 +1109,116 @@ function createUtils() { } }, }, + index: { + /** + * @param {Index} v + */ + serialize(v) { + switch (v) { + case /** @satisfies {DateIndex} */ (0): + return "dateindex"; + case /** @satisfies {DecadeIndex} */ (1): + return "decadeindex"; + case /** @satisfies {DifficultyEpoch} */ (2): + return "difficultyepoch"; + case /** @satisfies {EmptyOutputIndex} */ (3): + return "emptyoutputindex"; + case /** @satisfies {HalvingEpoch} */ (4): + return "halvingepoch"; + case /** @satisfies {Height} */ (5): + return "height"; + case /** @satisfies {InputIndex} */ (6): + return "inputindex"; + case /** @satisfies {MonthIndex} */ (7): + return "monthindex"; + case /** @satisfies {OpReturnIndex} */ (8): + return "opreturnindex"; + case /** @satisfies {OutputIndex} */ (9): + return "outputindex"; + case /** @satisfies {P2AIndex} */ (10): + return "p2aindex"; + case /** @satisfies {P2MSIndex} */ (11): + return "p2msindex"; + case /** @satisfies {P2PK33Index} */ (12): + return "p2pk33index"; + case /** @satisfies {P2PK65Index} */ (13): + return "p2pk65index"; + case /** @satisfies {P2PKHIndex} */ (14): + return "p2pkhindex"; + case /** @satisfies {P2SHIndex} */ (15): + return "p2shindex"; + case /** @satisfies {P2TRIndex} */ (16): + return "p2trindex"; + case /** @satisfies {P2WPKHIndex} */ (17): + return "p2wpkhindex"; + case /** @satisfies {P2WSHIndex} */ (18): + return "p2wshindex"; + case /** @satisfies {QuarterIndex} */ (19): + return "quarterindex"; + case /** @satisfies {TxIndex} */ (20): + return "txindex"; + case /** @satisfies {UnknownOutputIndex} */ (21): + return "unknownoutputindex"; + case /** @satisfies {WeekIndex} */ (22): + return "weekindex"; + case /** @satisfies {YearIndex} */ (23): + return "yearindex"; + } + }, + }, + chartableIndex: { + /** + * @param {Index} v + */ + serialize(v) { + switch (v) { + case /** @satisfies {DateIndex} */ (0): + return "date"; + case /** @satisfies {DecadeIndex} */ (1): + return "decade"; + // case /** @satisfies {DifficultyEpoch} */ (2): + // return "difficulty"; + // case /** @satisfies {HalvingEpoch} */ (4): + // return "halving"; + case /** @satisfies {Height} */ (5): + return "timestamp"; + case /** @satisfies {MonthIndex} */ (7): + return "month"; + case /** @satisfies {QuarterIndex} */ (19): + return "quarter"; + case /** @satisfies {WeekIndex} */ (22): + return "week"; + case /** @satisfies {YearIndex} */ (23): + return "year"; + default: + return null; + } + }, + /** + * @param {string} v + * @returns {ChartableIndex} + */ + deserialize(v) { + switch (v) { + case "timestamp": + return /** @satisfies {Height} */ (5); + case "date": + return /** @satisfies {DateIndex} */ (0); + case "week": + return /** @satisfies {WeekIndex} */ (22); + case "month": + return /** @satisfies {MonthIndex} */ (7); + case "quarter": + return /** @satisfies {QuarterIndex} */ (19); + case "year": + return /** @satisfies {YearIndex} */ (23); + case "decade": + return /** @satisfies {DecadeIndex} */ (1); + default: + throw Error("Unsupported"); + } + }, + }, }; const formatters = { @@ -1333,62 +1459,6 @@ function createUtils() { } } - /** - * @param {Index} index - */ - function vecIndexToString(index) { - switch (index) { - case /** @satisfies {DateIndex} */ (0): - return "dateindex"; - case /** @satisfies {DecadeIndex} */ (1): - return "decadeindex"; - case /** @satisfies {DifficultyEpoch} */ (2): - return "difficultyepoch"; - case /** @satisfies {EmptyOutputIndex} */ (3): - return "emptyoutputindex"; - case /** @satisfies {HalvingEpoch} */ (4): - return "halvingepoch"; - case /** @satisfies {Height} */ (5): - return "height"; - case /** @satisfies {InputIndex} */ (6): - return "inputindex"; - case /** @satisfies {MonthIndex} */ (7): - return "monthindex"; - case /** @satisfies {OpReturnIndex} */ (8): - return "opreturnindex"; - case /** @satisfies {OutputIndex} */ (9): - return "outputindex"; - case /** @satisfies {P2AIndex} */ (10): - return "p2aindex"; - case /** @satisfies {P2MSIndex} */ (11): - return "p2msindex"; - case /** @satisfies {P2PK33Index} */ (12): - return "p2pk33index"; - case /** @satisfies {P2PK65Index} */ (13): - return "p2pk65index"; - case /** @satisfies {P2PKHIndex} */ (14): - return "p2pkhindex"; - case /** @satisfies {P2SHIndex} */ (15): - return "p2shindex"; - case /** @satisfies {P2TRIndex} */ (16): - return "p2trindex"; - case /** @satisfies {P2WPKHIndex} */ (17): - return "p2wpkhindex"; - case /** @satisfies {P2WSHIndex} */ (18): - return "p2wshindex"; - case /** @satisfies {QuarterIndex} */ (19): - return "quarterindex"; - case /** @satisfies {TxIndex} */ (20): - return "txindex"; - case /** @satisfies {UnknownOutputIndex} */ (21): - return "unknownoutputindex"; - case /** @satisfies {WeekIndex} */ (22): - return "weekindex"; - case /** @satisfies {YearIndex} */ (23): - return "yearindex"; - } - } - /** * @param {Index} index * @param {VecId} vecId @@ -1396,7 +1466,7 @@ function createUtils() { * @param {number} [to] */ function genPath(index, vecId, from, to) { - let path = `/query?index=${vecIndexToString(index)}&values=${vecId}`; + let path = `/query?index=${serde.index.serialize(index)}&values=${vecId}`; if (from !== undefined) { path += `&from=${from}`; } @@ -2144,7 +2214,7 @@ function main() { colors, elements, lightweightCharts, - selected: /** @type {Accessor} */ ( + option: /** @type {Accessor} */ ( chartOption ), signals, diff --git a/websites/default/scripts/options.js b/websites/default/scripts/options.js index 7f242ba50..b7962c1d5 100644 --- a/websites/default/scripts/options.js +++ b/websites/default/scripts/options.js @@ -1,7 +1,7 @@ // @ts-check /** - * @typedef {Height | DateIndex | WeekIndex | DifficultyEpoch | MonthIndex | QuarterIndex | YearIndex | HalvingEpoch | DecadeIndex} ChartableIndex + * @typedef {Height | DateIndex | WeekIndex | MonthIndex | QuarterIndex | YearIndex | DecadeIndex} ChartableIndex */ /** * @template {readonly unknown[]} T