bitview: reorg part 5

This commit is contained in:
nym21
2025-09-26 00:04:14 +02:00
parent 0d5b792c57
commit dfe5148f17
21 changed files with 769 additions and 810 deletions
-281
View File
@@ -1,281 +0,0 @@
import { serdeIndex } from "./serde";
import { runWhenIdle } from "./timing";
/**
* @param {Signals} signals
* @param {Utilities} utils
* @param {Env} env
* @param {MetricToIndexes} metricToIndexes
*/
export function createVecsResources(signals, utils, env, metricToIndexes) {
const owner = signals.getOwner();
const defaultFrom = -10_000;
const defaultTo = undefined;
/**
* Defaults
* - from: -10_000
* - to: undefined
*
* @param {Object} [args]
* @param {number} [args.from]
* @param {number} [args.to]
*/
function genFetchedKey(args) {
return `${args?.from}-${args?.to}`;
}
const defaultFetchedKey = genFetchedKey({ from: defaultFrom, to: defaultTo });
/**
* @template {number | OHLCTuple} [T=number]
* @param {Index} index
* @param {Metric} metric
*/
function createVecResource(index, metric) {
if (env.localhost && !(metric in metricToIndexes)) {
throw Error(`${metric} not recognized`);
}
return signals.runWithOwner(owner, () => {
/** @typedef {T extends number ? SingleValueData : CandlestickData} Value */
const fetchedRecord = signals.createSignal(
/** @type {Map<string, {loading: boolean, at: Date | null, vec: Signal<T[] | null>}>} */ (
new Map()
),
);
return {
url: api.genUrl(index, metric, defaultFrom),
fetched: fetchedRecord,
/**
* Defaults
* - from: -10_000
* - to: undefined
*
* @param {Object} [args]
* @param {number} [args.from]
* @param {number} [args.to]
*/
async fetch(args) {
const from = args?.from ?? defaultFrom;
const to = args?.to ?? defaultTo;
const fetchedKey = genFetchedKey({ from, to });
if (!fetchedRecord().has(fetchedKey)) {
fetchedRecord.set((map) => {
map.set(fetchedKey, {
loading: false,
at: null,
vec: signals.createSignal(/** @type {T[] | null} */ (null), {
equals: false,
}),
});
return map;
});
}
const fetched = fetchedRecord().get(fetchedKey);
if (!fetched) throw Error("Unreachable");
if (fetched.loading) return fetched.vec();
if (fetched.at) {
const diff = new Date().getTime() - fetched.at.getTime();
const ONE_MINUTE_IN_MS = 60_000;
if (diff < ONE_MINUTE_IN_MS) return fetched.vec();
}
fetched.loading = true;
const res = /** @type {T[] | null} */ (
await api.fetchVec(
(values) => {
if (values.length || !fetched.vec()) {
fetched.vec.set(values);
}
},
index,
metric,
from,
to,
)
);
fetched.at = new Date();
fetched.loading = false;
return res;
},
};
});
}
/** @type {Map<string, NonNullable<ReturnType<typeof createVecResource>>>} */
const map = new Map();
const vecs = {
/**
* @template {number | OHLCTuple} [T=number]
* @param {Index} index
* @param {Metric} metric
*/
getOrCreate(index, metric) {
const key = `${index},${metric}`;
const found = map.get(key);
if (found) {
return found;
}
const vec = createVecResource(index, metric);
if (!vec) throw Error("vec is undefined");
map.set(key, /** @type {any} */ (vec));
return vec;
},
genFetchedKey,
defaultFetchedKey,
};
return vecs;
}
/** @typedef {ReturnType<typeof createVecsResources>} VecsResources */
/** @typedef {ReturnType<VecsResources["getOrCreate"]>} VecResource */
const CACHE_NAME = "api";
const API_VECS_PREFIX = "/api/vecs";
/**
* @template T
* @param {(value: T) => void} callback
* @param {string} path
* @param {boolean} [mustBeArray]
*/
async function fetchApi(callback, path, mustBeArray) {
const url = `${API_VECS_PREFIX}${path}`;
/** @type {T | null} */
let cachedJson = null;
/** @type {Cache | undefined} */
let cache;
try {
cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(url);
if (cachedResponse) {
console.debug(`cache: ${url}`);
const json = /** @type {T} */ await cachedResponse.json();
cachedJson = json;
callback(json);
}
} catch {}
if (navigator.onLine) {
// TODO: rerun after 10s instead of returning (due to some kind of error)
/** @type {Response | undefined} */
let fetchedResponse;
try {
fetchedResponse = await fetch(url, {
signal: AbortSignal.timeout(5000),
});
if (!fetchedResponse.ok) {
throw Error;
}
} catch {
return cachedJson;
}
const clonedResponse = fetchedResponse.clone();
let fetchedJson = /** @type {T | null} */ (null);
try {
const f = await fetchedResponse.json();
fetchedJson = /** @type {T} */ (
mustBeArray && !Array.isArray(f) ? [f] : f
);
} catch (_) {
return cachedJson;
}
if (!fetchedJson) return cachedJson;
console.debug(`fetch: ${url}`);
if (Array.isArray(cachedJson) && Array.isArray(fetchedJson)) {
const previousLength = cachedJson?.length || 0;
const newLength = fetchedJson.length;
if (!newLength) {
return cachedJson;
}
if (previousLength && previousLength === newLength) {
const previousLastValue = Object.values(cachedJson || []).at(-1);
const newLastValue = Object.values(fetchedJson).at(-1);
if (
JSON.stringify(previousLastValue) === JSON.stringify(newLastValue)
) {
return cachedJson;
}
}
}
callback(fetchedJson);
runWhenIdle(async function () {
try {
await cache?.put(url, clonedResponse);
} catch (_) {}
});
return fetchedJson;
} else {
return cachedJson;
}
}
/**
* @param {Index} index
* @param {Metric} metric
* @param {number} [from]
* @param {number} [to]
*/
function genPath(index, metric, from, to) {
let path = `/${serdeIndex.serialize(index)}-to-${metric.replaceAll("_", "-")}?`;
if (from !== undefined) {
path += `from=${from}`;
}
if (to !== undefined) {
if (!path.endsWith("?")) {
path += `&`;
}
path += `to=${to}`;
}
return path;
}
export const api = {
/**
* @param {Index} index
* @param {Metric} metric
* @param {number} from
*/
genUrl(index, metric, from) {
return `${API_VECS_PREFIX}${genPath(index, metric, from)}`;
},
/**
* @template {number | OHLCTuple} [T=number]
* @param {(v: T[]) => void} callback
* @param {Index} index
* @param {Metric} metric
* @param {number} [from]
* @param {number} [to]
*/
fetchVec(callback, index, metric, from, to) {
return fetchApi(callback, genPath(index, metric, from, to), true);
},
/**
* @template {number | OHLCTuple} [T=number]
* @param {(v: T) => void} callback
* @param {Index} index
* @param {Metric} metric
*/
fetchLast(callback, index, metric) {
return fetchApi(callback, genPath(index, metric, -1));
},
};
@@ -1,4 +1,4 @@
/** @import { IChartApi, ISeriesApi as _ISeriesApi, SeriesDefinition, SingleValueData as _SingleValueData, CandlestickData as _CandlestickData, BaselineData as _BaselineData, HistogramData as _HistogramData, SeriesType, IPaneApi, LineSeriesPartialOptions as _LineSeriesPartialOptions, HistogramSeriesPartialOptions as _HistogramSeriesPartialOptions, BaselineSeriesPartialOptions as _BaselineSeriesPartialOptions, CandlestickSeriesPartialOptions as _CandlestickSeriesPartialOptions, WhitespaceData, DeepPartial, ChartOptions, Time, LineData as _LineData } from '../packages/lightweight-charts/5.0.8/dist/typings' */
/** @import { IChartApi, ISeriesApi as _ISeriesApi, SeriesDefinition, SingleValueData as _SingleValueData, CandlestickData as _CandlestickData, BaselineData as _BaselineData, HistogramData as _HistogramData, SeriesType, IPaneApi, LineSeriesPartialOptions as _LineSeriesPartialOptions, HistogramSeriesPartialOptions as _HistogramSeriesPartialOptions, BaselineSeriesPartialOptions as _BaselineSeriesPartialOptions, CandlestickSeriesPartialOptions as _CandlestickSeriesPartialOptions, WhitespaceData, DeepPartial, ChartOptions, Time, LineData as _LineData } from '../../packages/lightweight-charts/5.0.8/dist/typings' */
import {
createChart,
@@ -7,20 +7,18 @@ import {
LineSeries,
BaselineSeries,
// } from "../packages/lightweight-charts/5.0.8/dist/lightweight-charts.standalone.development.mjs";
} from "../packages/lightweight-charts/5.0.8/dist/lightweight-charts.standalone.production.mjs";
} from "../../packages/lightweight-charts/5.0.8/dist/lightweight-charts.standalone.production.mjs";
import {
createHorizontalChoiceField,
createLabeledInput,
createSpanName,
} from "./dom";
import { createOklchToRGBA } from "./colors";
import { throttle } from "./timing";
import { serdeBool } from "./serde";
} from "../dom";
import { createOklchToRGBA } from "./oklch";
import { throttle } from "../timing";
import { serdeBool } from "../serde";
/**
* @typedef {[number, number, number, number]} OHLCTuple
*
* @typedef {Object} Valued
* @property {number} value
*
@@ -1003,7 +1001,7 @@ function numberToShortUSFormat(value, digits) {
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_000_000) {
} else if (absoluteValue >= 1_000_000_000_000_000_000_000) {
return "Inf.";
}
@@ -1015,11 +1013,6 @@ function numberToShortUSFormat(value, digits) {
const modulused = log % 3;
// return `${numberToUSFormat(
// value / (1_000_000 * 1_000 ** letterIndex),
// 3,
// )}${letter}`;
if (modulused === 0) {
return `${numberToUSFormat(
value / (1_000_000 * 1_000 ** letterIndex),
@@ -0,0 +1,100 @@
export function createOklchToRGBA() {
{
/**
*
* @param {readonly [number, number, number, number, number, number, number, number, number]} A
* @param {readonly [number, number, number]} B
* @returns
*/
function multiplyMatrices(A, B) {
return /** @type {const} */ ([
A[0] * B[0] + A[1] * B[1] + A[2] * B[2],
A[3] * B[0] + A[4] * B[1] + A[5] * B[2],
A[6] * B[0] + A[7] * B[1] + A[8] * B[2],
]);
}
/**
* @param {readonly [number, number, number]} param0
*/
function oklch2oklab([l, c, h]) {
return /** @type {const} */ ([
l,
isNaN(h) ? 0 : c * Math.cos((h * Math.PI) / 180),
isNaN(h) ? 0 : c * Math.sin((h * Math.PI) / 180),
]);
}
/**
* @param {readonly [number, number, number]} rgb
*/
function srgbLinear2rgb(rgb) {
return rgb.map((c) =>
Math.abs(c) > 0.0031308
? (c < 0 ? -1 : 1) * (1.055 * Math.abs(c) ** (1 / 2.4) - 0.055)
: 12.92 * c,
);
}
/**
* @param {readonly [number, number, number]} lab
*/
function oklab2xyz(lab) {
const LMSg = multiplyMatrices(
/** @type {const} */ ([
1, 0.3963377773761749, 0.2158037573099136, 1, -0.1055613458156586,
-0.0638541728258133, 1, -0.0894841775298119, -1.2914855480194092,
]),
lab,
);
const LMS = /** @type {[number, number, number]} */ (
LMSg.map((val) => val ** 3)
);
return multiplyMatrices(
/** @type {const} */ ([
1.2268798758459243, -0.5578149944602171, 0.2813910456659647,
-0.0405757452148008, 1.112286803280317, -0.0717110580655164,
-0.0763729366746601, -0.4214933324022432, 1.5869240198367816,
]),
LMS,
);
}
/**
* @param {readonly [number, number, number]} xyz
*/
function xyz2rgbLinear(xyz) {
return multiplyMatrices(
[
3.2409699419045226, -1.537383177570094, -0.4986107602930034,
-0.9692436362808796, 1.8759675015077202, 0.04155505740717559,
0.05563007969699366, -0.20397695888897652, 1.0569715142428786,
],
xyz,
);
}
/** @param {string} oklch */
return function (oklch) {
oklch = oklch.replace("oklch(", "");
oklch = oklch.replace(")", "");
let splitOklch = oklch.split(" / ");
let alpha = 1;
if (splitOklch.length === 2) {
alpha = Number(splitOklch.pop()?.replace("%", "")) / 100;
}
splitOklch = oklch.split(" ");
const lch = splitOklch.map((v, i) => {
if (!i && v.includes("%")) {
return Number(v.replace("%", "")) / 100;
} else {
return Number(v);
}
});
const rgb = srgbLinear2rgb(
xyz2rgbLinear(
oklab2xyz(oklch2oklab(/** @type {[number, number, number]} */ (lch))),
),
).map((v) => {
return Math.max(Math.min(Math.round(v * 255), 255), 0);
});
return [...rgb, alpha];
};
}
}
-101
View File
@@ -114,104 +114,3 @@ export function createColors(dark) {
* @typedef {Colors["orange"]} Color
* @typedef {keyof Colors} ColorName
*/
export function createOklchToRGBA() {
{
/**
*
* @param {readonly [number, number, number, number, number, number, number, number, number]} A
* @param {readonly [number, number, number]} B
* @returns
*/
function multiplyMatrices(A, B) {
return /** @type {const} */ ([
A[0] * B[0] + A[1] * B[1] + A[2] * B[2],
A[3] * B[0] + A[4] * B[1] + A[5] * B[2],
A[6] * B[0] + A[7] * B[1] + A[8] * B[2],
]);
}
/**
* @param {readonly [number, number, number]} param0
*/
function oklch2oklab([l, c, h]) {
return /** @type {const} */ ([
l,
isNaN(h) ? 0 : c * Math.cos((h * Math.PI) / 180),
isNaN(h) ? 0 : c * Math.sin((h * Math.PI) / 180),
]);
}
/**
* @param {readonly [number, number, number]} rgb
*/
function srgbLinear2rgb(rgb) {
return rgb.map((c) =>
Math.abs(c) > 0.0031308
? (c < 0 ? -1 : 1) * (1.055 * Math.abs(c) ** (1 / 2.4) - 0.055)
: 12.92 * c,
);
}
/**
* @param {readonly [number, number, number]} lab
*/
function oklab2xyz(lab) {
const LMSg = multiplyMatrices(
/** @type {const} */ ([
1, 0.3963377773761749, 0.2158037573099136, 1, -0.1055613458156586,
-0.0638541728258133, 1, -0.0894841775298119, -1.2914855480194092,
]),
lab,
);
const LMS = /** @type {[number, number, number]} */ (
LMSg.map((val) => val ** 3)
);
return multiplyMatrices(
/** @type {const} */ ([
1.2268798758459243, -0.5578149944602171, 0.2813910456659647,
-0.0405757452148008, 1.112286803280317, -0.0717110580655164,
-0.0763729366746601, -0.4214933324022432, 1.5869240198367816,
]),
LMS,
);
}
/**
* @param {readonly [number, number, number]} xyz
*/
function xyz2rgbLinear(xyz) {
return multiplyMatrices(
[
3.2409699419045226, -1.537383177570094, -0.4986107602930034,
-0.9692436362808796, 1.8759675015077202, 0.04155505740717559,
0.05563007969699366, -0.20397695888897652, 1.0569715142428786,
],
xyz,
);
}
/** @param {string} oklch */
return function (oklch) {
oklch = oklch.replace("oklch(", "");
oklch = oklch.replace(")", "");
let splitOklch = oklch.split(" / ");
let alpha = 1;
if (splitOklch.length === 2) {
alpha = Number(splitOklch.pop()?.replace("%", "")) / 100;
}
splitOklch = oklch.split(" ");
const lch = splitOklch.map((v, i) => {
if (!i && v.includes("%")) {
return Number(v.replace("%", "")) / 100;
} else {
return Number(v);
}
});
const rgb = srgbLinear2rgb(
xyz2rgbLinear(
oklab2xyz(oklch2oklab(/** @type {[number, number, number]} */ (lch))),
),
).map((v) => {
return Math.max(Math.min(Math.round(v * 255), 255), 0);
});
return [...rgb, alpha];
};
}
}
File diff suppressed because it is too large Load Diff
+2 -66
View File
@@ -101,9 +101,9 @@ export const serdeBool = {
* @param {string} v
*/
deserialize(v) {
if (v === "true") {
if (v === "true" || v === "1") {
return true;
} else if (v === "false") {
} else if (v === "false" || v === "0") {
return false;
} else {
throw "deser bool err";
@@ -111,70 +111,6 @@ export const serdeBool = {
},
};
export const serdeIndex = {
/**
* @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 {P2AAddressIndex} */ (10):
return "p2aaddressindex";
case /** @satisfies {P2MSOutputIndex} */ (11):
return "p2msoutputindex";
case /** @satisfies {P2PK33AddressIndex} */ (12):
return "p2pk33addressindex";
case /** @satisfies {P2PK65AddressIndex} */ (13):
return "p2pk65addressindex";
case /** @satisfies {P2PKHAddressIndex} */ (14):
return "p2pkhaddressindex";
case /** @satisfies {P2SHAddressIndex} */ (15):
return "p2shaddressindex";
case /** @satisfies {P2TRAddressIndex} */ (16):
return "p2traddressindex";
case /** @satisfies {P2WPKHAddressIndex} */ (17):
return "p2wpkhaddressindex";
case /** @satisfies {P2WSHAddressIndex} */ (18):
return "p2wshaddressindex";
case /** @satisfies {QuarterIndex} */ (19):
return "quarterindex";
case /** @satisfies {SemesterIndex} */ (20):
return "semesterindex";
case /** @satisfies {TxIndex} */ (21):
return "txindex";
case /** @satisfies {UnknownOutputIndex} */ (22):
return "unknownoutputindex";
case /** @satisfies {WeekIndex} */ (23):
return "weekindex";
case /** @satisfies {YearIndex} */ (24):
return "yearindex";
case /** @satisfies {LoadedAddressIndex} */ (25):
return "loadedaddressindex";
case /** @satisfies {EmptyAddressIndex} */ (26):
return "emptyaddressindex";
}
},
};
export const serdeChartableIndex = {
/**
* @param {number} v
-12
View File
@@ -36,15 +36,3 @@ export function throttle(callback, wait = 1000) {
}
};
}
/**
* @param {VoidFunction} callback
* @param {number} [timeout = 1]
*/
export function runWhenIdle(callback, timeout = 1) {
if ("requestIdleCallback" in window) {
requestIdleCallback(callback);
} else {
setTimeout(callback, timeout);
}
}