heatmaps: part 11

This commit is contained in:
nym21
2026-06-01 01:04:14 +02:00
parent 7181d59966
commit 087a3b6fd6
3 changed files with 80 additions and 33 deletions
+45 -4
View File
@@ -116,6 +116,7 @@ function loadRange() {
}
let cursor = 0;
let needsRebuild = false;
const workers = Array.from({
length: Math.min(MAX_PARALLEL_FETCHES, missing.length),
}).map(async () => {
@@ -123,10 +124,17 @@ function loadRange() {
while (index !== undefined) {
const entry = missing[index];
try {
const points = await option.points.fetch(entry.date, controller.signal);
const points = await option.points.fetch(
entry.date,
controller.signal,
(points) => {
if (isCurrentLoad(option, controller, generation)) {
setPoints(entry, points);
}
},
);
if (isCurrentLoad(option, controller, generation)) {
pointsByDate.set(entry.date, points);
addDateToGrid(entry.dateIndex, points);
setPoints(entry, points);
}
} catch (error) {
if (controller.signal.aborted) return;
@@ -147,7 +155,11 @@ function loadRange() {
void Promise.all(workers).then(() => {
if (isCurrentLoad(option, controller, generation)) {
updateStatus(completed, currentDates.length, failed);
paint();
if (needsRebuild) {
rebuildGrid();
} else {
paint();
}
}
});
@@ -157,6 +169,21 @@ function loadRange() {
cursor += 1;
return index;
}
/**
* @param {{ date: string, dateIndex: number }} entry
* @param {HeatmapPoints} points
*/
function setPoints(entry, points) {
const previous = pointsByDate.get(entry.date);
if (previous && samePoints(previous, points)) return;
pointsByDate.set(entry.date, points);
if (previous) {
needsRebuild = true;
} else {
addDateToGrid(entry.dateIndex, points);
}
}
}
/**
@@ -209,6 +236,20 @@ function addDateToGrid(dateIndex, points) {
if (dirtyCol !== undefined) schedulePaint(dirtyCol);
}
/**
* @param {HeatmapPoints} a
* @param {HeatmapPoints} b
*/
function samePoints(a, b) {
if (a === b) return true;
if (a.kind !== b.kind || a.values !== b.values) return false;
if (a.kind === "implicit" && b.kind === "implicit") {
return a.yStart === b.yStart && a.yStep === b.yStep;
}
if (a.kind === "explicit" && b.kind === "explicit") return a.y === b.y;
return false;
}
/** @param {number} col */
function schedulePaint(col) {
dirtyCols.add(col);
+34 -28
View File
@@ -9,6 +9,7 @@ import { defaultTooltip } from "./tooltip.js";
const BINS = 2400;
const MIN_LOG = -8;
const BINS_PER_DECADE = 200;
const MAX_LOG = MIN_LOG + (BINS - 1) / BINS_PER_DECADE;
export const oracleRawHeatmapOption = createOracleHeatmapOption("raw", "Raw");
export const oracleEmaHeatmapOption = createOracleHeatmapOption("ema", "EMA");
@@ -24,7 +25,8 @@ function createOracleHeatmapOption(mode, name) {
name,
title: `Oracle ${name} Histogram`,
points: {
fetch: (date, signal) => fetchOraclePoints(mode, date, signal),
fetch: (date, signal, onPoints) =>
fetchOraclePoints(mode, date, signal, onPoints),
},
grid: createAverageGrid({
yStart: MIN_LOG,
@@ -40,40 +42,44 @@ function createOracleHeatmapOption(mode, name) {
* @param {"raw" | "ema"} mode
* @param {string} date
* @param {AbortSignal} signal
* @param {(points: HeatmapPoints) => void} [onPoints]
* @returns {Promise<HeatmapPoints>}
*/
async function fetchOraclePoints(mode, date, signal) {
const values = await firstAvailable((onValue) =>
mode === "raw"
? brk.getOracleHistogramRaw(date, { signal, onValue })
: brk.getOracleHistogramEma(date, { signal, onValue }),
async function fetchOraclePoints(mode, date, signal, onPoints) {
const values = await fetchOracleValues(
mode,
date,
signal,
onPoints ? (values) => onPoints(toOraclePoints(values)) : undefined,
);
return {
kind: "implicit",
yStart: MIN_LOG,
yStep: 1 / BINS_PER_DECADE,
values,
};
return toOraclePoints(values);
}
/**
* @param {(onValue: (value: number[]) => void) => Promise<number[]>} fetch
* @param {"raw" | "ema"} mode
* @param {string} date
* @param {AbortSignal} signal
* @param {(values: number[]) => void} [onValue]
* @returns {Promise<number[]>}
*/
function firstAvailable(fetch) {
return new Promise((resolve, reject) => {
let settled = false;
/** @param {number[]} value */
const resolveOnce = (value) => {
if (settled) return;
settled = true;
resolve(value);
};
fetch(resolveOnce).then(resolveOnce, (error) => {
if (!settled) reject(error);
});
});
function fetchOracleValues(mode, date, signal, onValue) {
return (
mode === "raw"
? brk.getOracleHistogramRaw(date, { signal, onValue })
: brk.getOracleHistogramEma(date, { signal, onValue })
);
}
/**
* @param {number[]} values
* @returns {HeatmapPoints}
*/
function toOraclePoints(values) {
return {
kind: "implicit",
yStart: MAX_LOG,
yStep: -1 / BINS_PER_DECADE,
values,
};
}
+1 -1
View File
@@ -13,7 +13,7 @@
* @typedef {HeatmapImplicitPoints | HeatmapExplicitPoints} HeatmapPoints
*
* @typedef {Object} HeatmapPointSource
* @property {(date: string, signal: AbortSignal) => Promise<HeatmapPoints>} fetch
* @property {(date: string, signal: AbortSignal, onPoints?: (points: HeatmapPoints) => void) => Promise<HeatmapPoints>} fetch
*
* @typedef {Object} HeatmapRange
* @property {number} start