mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-25 09:48:10 -07:00
website: snapshot
This commit is contained in:
+246
-289
@@ -76,15 +76,13 @@ const lineWidth = /** @type {any} */ (1.5);
|
|||||||
* @param {Object} args
|
* @param {Object} args
|
||||||
* @param {string} args.id
|
* @param {string} args.id
|
||||||
* @param {HTMLElement} args.parent
|
* @param {HTMLElement} args.parent
|
||||||
* @param {Signals} args.signals
|
|
||||||
* @param {BrkClient} args.brk
|
* @param {BrkClient} args.brk
|
||||||
* @param {true} [args.fitContent]
|
* @param {true} [args.fitContent]
|
||||||
* @param {HTMLElement} [args.captureElement]
|
* @param {HTMLElement} [args.captureElement]
|
||||||
* @param {{unit: Unit; blueprints: AnySeriesBlueprint[]}[]} [args.config]
|
* @param {{unit: Unit; blueprints: AnyFetchedSeriesBlueprint[]}[]} [args.config]
|
||||||
*/
|
*/
|
||||||
export function createChart({
|
export function createChart({
|
||||||
parent,
|
parent,
|
||||||
signals,
|
|
||||||
id: chartId,
|
id: chartId,
|
||||||
brk,
|
brk,
|
||||||
fitContent,
|
fitContent,
|
||||||
@@ -418,8 +416,7 @@ export function createChart({
|
|||||||
* @param {number} args.order
|
* @param {number} args.order
|
||||||
* @param {Color[]} args.colors
|
* @param {Color[]} args.colors
|
||||||
* @param {LCSeriesType} args.seriesType
|
* @param {LCSeriesType} args.seriesType
|
||||||
* @param {AnyMetricPattern} [args.metric]
|
* @param {AnyMetricPattern} args.metric
|
||||||
* @param {Accessor<WhitespaceData[]>} [args.data]
|
|
||||||
* @param {number} args.paneIndex
|
* @param {number} args.paneIndex
|
||||||
* @param {boolean} [args.defaultActive]
|
* @param {boolean} [args.defaultActive]
|
||||||
* @param {(order: number) => void} args.setOrder
|
* @param {(order: number) => void} args.setOrder
|
||||||
@@ -442,7 +439,6 @@ export function createChart({
|
|||||||
paneIndex,
|
paneIndex,
|
||||||
defaultActive,
|
defaultActive,
|
||||||
colors,
|
colors,
|
||||||
data,
|
|
||||||
setOrder,
|
setOrder,
|
||||||
show,
|
show,
|
||||||
hide,
|
hide,
|
||||||
@@ -454,273 +450,243 @@ export function createChart({
|
|||||||
onRemove,
|
onRemove,
|
||||||
onDataLoaded,
|
onDataLoaded,
|
||||||
}) {
|
}) {
|
||||||
return signals.createRoot((dispose) => {
|
const key = stringToId(name);
|
||||||
const key = stringToId(name);
|
const id = `${key}-${paneIndex}`;
|
||||||
const id = `${key}-${paneIndex}`;
|
|
||||||
|
|
||||||
// Reuse existing state if same name (links legends across panes)
|
// Reuse existing state if same name (links legends across panes)
|
||||||
const existingActive = sharedActiveStates.get(key);
|
const existingActive = sharedActiveStates.get(key);
|
||||||
const active =
|
const active =
|
||||||
existingActive ??
|
existingActive ??
|
||||||
createPersistedValue({
|
createPersistedValue({
|
||||||
defaultValue: defaultActive ?? true,
|
defaultValue: defaultActive ?? true,
|
||||||
storageKey: id,
|
storageKey: id,
|
||||||
urlKey: key,
|
urlKey: key,
|
||||||
...serdeBool,
|
...serdeBool,
|
||||||
|
});
|
||||||
|
if (!existingActive) sharedActiveStates.set(key, active);
|
||||||
|
|
||||||
|
setOrder(-order);
|
||||||
|
|
||||||
|
active.value ? show() : hide();
|
||||||
|
|
||||||
|
let hasData = false;
|
||||||
|
let lastTime = -Infinity;
|
||||||
|
|
||||||
|
/** @type {VoidFunction | null} */
|
||||||
|
let _fetch = null;
|
||||||
|
|
||||||
|
/** @type {AnySeries} */
|
||||||
|
const series = {
|
||||||
|
active,
|
||||||
|
setActive(value) {
|
||||||
|
const wasActive = active.value;
|
||||||
|
active.set(value);
|
||||||
|
seriesByKey.get(key)?.forEach((s) => {
|
||||||
|
value ? s.show() : s.hide();
|
||||||
});
|
});
|
||||||
if (!existingActive) sharedActiveStates.set(key, active);
|
document.querySelectorAll(`[data-series="${id}"]`).forEach((el) => {
|
||||||
|
if (el instanceof HTMLInputElement && el.type === "checkbox") {
|
||||||
setOrder(-order);
|
el.checked = value;
|
||||||
|
|
||||||
active.value ? show() : hide();
|
|
||||||
|
|
||||||
let hasData = false;
|
|
||||||
let lastTime = -Infinity;
|
|
||||||
|
|
||||||
/** @type {VoidFunction | null} */
|
|
||||||
let _fetch = null;
|
|
||||||
|
|
||||||
/** @type {AnySeries} */
|
|
||||||
const series = {
|
|
||||||
active,
|
|
||||||
setActive(value) {
|
|
||||||
const wasActive = active.value;
|
|
||||||
active.set(value);
|
|
||||||
seriesByKey.get(key)?.forEach((s) => {
|
|
||||||
value ? s.show() : s.hide();
|
|
||||||
});
|
|
||||||
document.querySelectorAll(`[data-series="${id}"]`).forEach((el) => {
|
|
||||||
if (el instanceof HTMLInputElement && el.type === "checkbox") {
|
|
||||||
el.checked = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (value && !wasActive) _fetch?.();
|
|
||||||
},
|
|
||||||
setOrder,
|
|
||||||
show,
|
|
||||||
hide,
|
|
||||||
highlight,
|
|
||||||
tame,
|
|
||||||
hasData: () => hasData,
|
|
||||||
fetch: () => _fetch?.(),
|
|
||||||
key,
|
|
||||||
id,
|
|
||||||
paneIndex,
|
|
||||||
url: null,
|
|
||||||
getData,
|
|
||||||
update,
|
|
||||||
remove() {
|
|
||||||
dispose();
|
|
||||||
onRemove();
|
|
||||||
seriesByKey.get(key)?.delete(series);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Register series for cross-pane linking
|
|
||||||
let keySet = seriesByKey.get(key);
|
|
||||||
if (!keySet) {
|
|
||||||
keySet = new Set();
|
|
||||||
seriesByKey.set(key, keySet);
|
|
||||||
}
|
|
||||||
keySet.add(series);
|
|
||||||
|
|
||||||
if (metric) {
|
|
||||||
/** @param {ChartableIndex} idx */
|
|
||||||
function setupIndexEffect(idx) {
|
|
||||||
// Reset data state for new index
|
|
||||||
hasData = false;
|
|
||||||
lastTime = -Infinity;
|
|
||||||
_fetch = null;
|
|
||||||
|
|
||||||
// Get timestamp metric from tree based on index type
|
|
||||||
const timeMetric =
|
|
||||||
idx === "height"
|
|
||||||
? brk.metrics.blocks.time.timestampMonotonic
|
|
||||||
: brk.metrics.blocks.time.timestamp;
|
|
||||||
const valuesMetric = /** @type {AnyMetricPattern} */ (metric);
|
|
||||||
const _timeEndpoint = timeMetric.get(idx);
|
|
||||||
if (!_timeEndpoint) throw "Expect time endpoint";
|
|
||||||
const timeEndpoint = _timeEndpoint;
|
|
||||||
const valuesEndpoint = valuesMetric.by[idx];
|
|
||||||
// Gracefully skip - series may be about to be removed by option change
|
|
||||||
if (!timeEndpoint || !valuesEndpoint) return;
|
|
||||||
|
|
||||||
series.url = `${
|
|
||||||
brk.baseUrl.endsWith("/") ? brk.baseUrl.slice(0, -1) : brk.baseUrl
|
|
||||||
}${valuesEndpoint.path}`;
|
|
||||||
|
|
||||||
(paneIndex ? legendBottom : legendTop).addOrReplace({
|
|
||||||
series,
|
|
||||||
name,
|
|
||||||
colors,
|
|
||||||
order,
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number[]} indexes
|
|
||||||
* @param {(number | null | [number, number, number, number])[]} values
|
|
||||||
*/
|
|
||||||
function processData(indexes, values) {
|
|
||||||
const length = Math.min(indexes.length, values.length);
|
|
||||||
|
|
||||||
// Find start index for processing
|
|
||||||
let startIdx = 0;
|
|
||||||
if (hasData) {
|
|
||||||
// Binary search to find first index where time >= lastTime
|
|
||||||
let lo = 0;
|
|
||||||
let hi = length;
|
|
||||||
while (lo < hi) {
|
|
||||||
const mid = (lo + hi) >>> 1;
|
|
||||||
if (indexes[mid] < lastTime) {
|
|
||||||
lo = mid + 1;
|
|
||||||
} else {
|
|
||||||
hi = mid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
startIdx = lo;
|
|
||||||
if (startIdx >= length) return; // No new data
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} i
|
|
||||||
* @returns {LineData | CandlestickData}
|
|
||||||
*/
|
|
||||||
function buildDataPoint(i) {
|
|
||||||
const time = /** @type {Time} */ (indexes[i]);
|
|
||||||
const v = values[i];
|
|
||||||
if (v === null) {
|
|
||||||
return { time, value: NaN };
|
|
||||||
} else if (typeof v === "number") {
|
|
||||||
return { time, value: v };
|
|
||||||
} else {
|
|
||||||
if (!Array.isArray(v) || v.length !== 4)
|
|
||||||
throw new Error(`Expected OHLC tuple, got: ${v}`);
|
|
||||||
const [open, high, low, close] = v;
|
|
||||||
return { time, open, high, low, close };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasData) {
|
|
||||||
// Initial load: build full array
|
|
||||||
const data = /** @type {LineData[] | CandlestickData[]} */ (
|
|
||||||
Array.from({ length })
|
|
||||||
);
|
|
||||||
|
|
||||||
let prevTime = null;
|
|
||||||
let timeOffset = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
const time = indexes[i];
|
|
||||||
const sameTime = prevTime === time;
|
|
||||||
if (sameTime) {
|
|
||||||
timeOffset += 1;
|
|
||||||
}
|
|
||||||
const offsetedI = i - timeOffset;
|
|
||||||
const point = buildDataPoint(i);
|
|
||||||
if (sameTime && "open" in point) {
|
|
||||||
const prev = /** @type {CandlestickData} */ (data[offsetedI]);
|
|
||||||
point.open = prev.open;
|
|
||||||
point.high = Math.max(prev.high, point.high);
|
|
||||||
point.low = Math.min(prev.low, point.low);
|
|
||||||
}
|
|
||||||
data[offsetedI] = point;
|
|
||||||
prevTime = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.length -= timeOffset;
|
|
||||||
|
|
||||||
setData(data);
|
|
||||||
hasData = true;
|
|
||||||
lastTime = /** @type {number} */ (data.at(-1)?.time) ?? -Infinity;
|
|
||||||
|
|
||||||
// Restore saved range or use defaults
|
|
||||||
const savedRange = getRange();
|
|
||||||
if (savedRange) {
|
|
||||||
ichart.timeScale().setVisibleLogicalRange({
|
|
||||||
from: savedRange.from,
|
|
||||||
to: savedRange.to,
|
|
||||||
});
|
|
||||||
} else if (fitContent) {
|
|
||||||
ichart.timeScale().fitContent();
|
|
||||||
} else if (
|
|
||||||
idx === "quarterindex" ||
|
|
||||||
idx === "semesterindex" ||
|
|
||||||
idx === "yearindex" ||
|
|
||||||
idx === "decadeindex"
|
|
||||||
) {
|
|
||||||
ichart
|
|
||||||
.timeScale()
|
|
||||||
.setVisibleLogicalRange({ from: -1, to: data.length });
|
|
||||||
}
|
|
||||||
// Delay until chart has applied the range
|
|
||||||
requestAnimationFrame(() => onDataLoaded?.());
|
|
||||||
} else {
|
|
||||||
// Incremental update: only process new data points
|
|
||||||
for (let i = startIdx; i < length; i++) {
|
|
||||||
const point = buildDataPoint(i);
|
|
||||||
update(point);
|
|
||||||
lastTime = /** @type {number} */ (point.time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchAndProcess() {
|
|
||||||
const [timeResult, valuesResult] = await Promise.all([
|
|
||||||
timeEndpoint.slice(-10000).fetch(),
|
|
||||||
valuesEndpoint?.slice(-10000).fetch(),
|
|
||||||
]);
|
|
||||||
if (timeResult?.data?.length && valuesResult?.data?.length) {
|
|
||||||
processData(timeResult.data, valuesResult.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_fetch = fetchAndProcess;
|
|
||||||
|
|
||||||
// Initial fetch if active
|
|
||||||
if (active.value) {
|
|
||||||
fetchAndProcess();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setupIndexEffect(index());
|
|
||||||
// Series don't subscribe to onIndexChange - panes recreates them on index change
|
|
||||||
// onIndexChange.add(setupIndexEffect);
|
|
||||||
// _cleanup = () => onIndexChange.delete(setupIndexEffect);
|
|
||||||
} else {
|
|
||||||
(paneIndex ? legendBottom : legendTop).addOrReplace({
|
|
||||||
series,
|
|
||||||
name,
|
|
||||||
colors,
|
|
||||||
order,
|
|
||||||
});
|
});
|
||||||
|
if (value && !wasActive) _fetch?.();
|
||||||
|
},
|
||||||
|
setOrder,
|
||||||
|
show,
|
||||||
|
hide,
|
||||||
|
highlight,
|
||||||
|
tame,
|
||||||
|
hasData: () => hasData,
|
||||||
|
fetch: () => _fetch?.(),
|
||||||
|
key,
|
||||||
|
id,
|
||||||
|
paneIndex,
|
||||||
|
url: null,
|
||||||
|
getData,
|
||||||
|
update,
|
||||||
|
remove() {
|
||||||
|
onRemove();
|
||||||
|
seriesByKey.get(key)?.delete(series);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
if (data) {
|
// Register series for cross-pane linking
|
||||||
signals.createEffect(data, (data) => {
|
let keySet = seriesByKey.get(key);
|
||||||
setData(data);
|
if (!keySet) {
|
||||||
hasData = true;
|
keySet = new Set();
|
||||||
const savedRange = getRange();
|
seriesByKey.set(key, keySet);
|
||||||
if (savedRange) {
|
}
|
||||||
ichart.timeScale().setVisibleLogicalRange({
|
keySet.add(series);
|
||||||
from: savedRange.from,
|
|
||||||
to: savedRange.to,
|
|
||||||
});
|
|
||||||
} else if (fitContent) {
|
|
||||||
ichart.timeScale().fitContent();
|
|
||||||
}
|
|
||||||
// Delay until chart has applied the range
|
|
||||||
requestAnimationFrame(() => onDataLoaded?.());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addPriceScaleSelectorIfNeeded({
|
/** @param {ChartableIndex} idx */
|
||||||
paneIndex,
|
function setupIndexEffect(idx) {
|
||||||
seriesType,
|
// Reset data state for new index
|
||||||
unit,
|
hasData = false;
|
||||||
|
lastTime = -Infinity;
|
||||||
|
_fetch = null;
|
||||||
|
|
||||||
|
// Get timestamp metric from tree based on index type
|
||||||
|
const timeMetric =
|
||||||
|
idx === "height"
|
||||||
|
? brk.metrics.blocks.time.timestampMonotonic
|
||||||
|
: brk.metrics.blocks.time.timestamp;
|
||||||
|
const valuesMetric = /** @type {AnyMetricPattern} */ (metric);
|
||||||
|
const _timeEndpoint = timeMetric.get(idx);
|
||||||
|
if (!_timeEndpoint) throw "Expect time endpoint";
|
||||||
|
const timeEndpoint = _timeEndpoint;
|
||||||
|
const valuesEndpoint = valuesMetric.by[idx];
|
||||||
|
// Gracefully skip - series may be about to be removed by option change
|
||||||
|
if (!timeEndpoint || !valuesEndpoint) return;
|
||||||
|
|
||||||
|
series.url = `${
|
||||||
|
brk.baseUrl.endsWith("/") ? brk.baseUrl.slice(0, -1) : brk.baseUrl
|
||||||
|
}${valuesEndpoint.path}`;
|
||||||
|
|
||||||
|
(paneIndex ? legendBottom : legendTop).addOrReplace({
|
||||||
|
series,
|
||||||
|
name,
|
||||||
|
colors,
|
||||||
|
order,
|
||||||
});
|
});
|
||||||
|
|
||||||
return series;
|
/**
|
||||||
|
* @param {number[]} indexes
|
||||||
|
* @param {(number | null | [number, number, number, number])[]} values
|
||||||
|
*/
|
||||||
|
function processData(indexes, values) {
|
||||||
|
const length = Math.min(indexes.length, values.length);
|
||||||
|
|
||||||
|
// Find start index for processing
|
||||||
|
let startIdx = 0;
|
||||||
|
if (hasData) {
|
||||||
|
// Binary search to find first index where time >= lastTime
|
||||||
|
let lo = 0;
|
||||||
|
let hi = length;
|
||||||
|
while (lo < hi) {
|
||||||
|
const mid = (lo + hi) >>> 1;
|
||||||
|
if (indexes[mid] < lastTime) {
|
||||||
|
lo = mid + 1;
|
||||||
|
} else {
|
||||||
|
hi = mid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startIdx = lo;
|
||||||
|
if (startIdx >= length) return; // No new data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} i
|
||||||
|
* @returns {LineData | CandlestickData}
|
||||||
|
*/
|
||||||
|
function buildDataPoint(i) {
|
||||||
|
const time = /** @type {Time} */ (indexes[i]);
|
||||||
|
const v = values[i];
|
||||||
|
if (v === null) {
|
||||||
|
return { time, value: NaN };
|
||||||
|
} else if (typeof v === "number") {
|
||||||
|
return { time, value: v };
|
||||||
|
} else {
|
||||||
|
if (!Array.isArray(v) || v.length !== 4)
|
||||||
|
throw new Error(`Expected OHLC tuple, got: ${v}`);
|
||||||
|
const [open, high, low, close] = v;
|
||||||
|
return { time, open, high, low, close };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasData) {
|
||||||
|
// Initial load: build full array
|
||||||
|
const data = /** @type {LineData[] | CandlestickData[]} */ (
|
||||||
|
Array.from({ length })
|
||||||
|
);
|
||||||
|
|
||||||
|
let prevTime = null;
|
||||||
|
let timeOffset = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const time = indexes[i];
|
||||||
|
const sameTime = prevTime === time;
|
||||||
|
if (sameTime) {
|
||||||
|
timeOffset += 1;
|
||||||
|
}
|
||||||
|
const offsetedI = i - timeOffset;
|
||||||
|
const point = buildDataPoint(i);
|
||||||
|
if (sameTime && "open" in point) {
|
||||||
|
const prev = /** @type {CandlestickData} */ (data[offsetedI]);
|
||||||
|
point.open = prev.open;
|
||||||
|
point.high = Math.max(prev.high, point.high);
|
||||||
|
point.low = Math.min(prev.low, point.low);
|
||||||
|
}
|
||||||
|
data[offsetedI] = point;
|
||||||
|
prevTime = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.length -= timeOffset;
|
||||||
|
|
||||||
|
setData(data);
|
||||||
|
hasData = true;
|
||||||
|
lastTime = /** @type {number} */ (data.at(-1)?.time) ?? -Infinity;
|
||||||
|
|
||||||
|
// Restore saved range or use defaults
|
||||||
|
const savedRange = getRange();
|
||||||
|
if (savedRange) {
|
||||||
|
ichart.timeScale().setVisibleLogicalRange({
|
||||||
|
from: savedRange.from,
|
||||||
|
to: savedRange.to,
|
||||||
|
});
|
||||||
|
} else if (fitContent) {
|
||||||
|
ichart.timeScale().fitContent();
|
||||||
|
} else if (
|
||||||
|
idx === "quarterindex" ||
|
||||||
|
idx === "semesterindex" ||
|
||||||
|
idx === "yearindex" ||
|
||||||
|
idx === "decadeindex"
|
||||||
|
) {
|
||||||
|
ichart
|
||||||
|
.timeScale()
|
||||||
|
.setVisibleLogicalRange({ from: -1, to: data.length });
|
||||||
|
}
|
||||||
|
// Delay until chart has applied the range
|
||||||
|
requestAnimationFrame(() => onDataLoaded?.());
|
||||||
|
} else {
|
||||||
|
// Incremental update: only process new data points
|
||||||
|
for (let i = startIdx; i < length; i++) {
|
||||||
|
const point = buildDataPoint(i);
|
||||||
|
update(point);
|
||||||
|
lastTime = /** @type {number} */ (point.time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAndProcess() {
|
||||||
|
const [timeResult, valuesResult] = await Promise.all([
|
||||||
|
timeEndpoint.slice(-10000).fetch(),
|
||||||
|
valuesEndpoint?.slice(-10000).fetch(),
|
||||||
|
]);
|
||||||
|
if (timeResult?.data?.length && valuesResult?.data?.length) {
|
||||||
|
processData(timeResult.data, valuesResult.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_fetch = fetchAndProcess;
|
||||||
|
|
||||||
|
// Initial fetch if active
|
||||||
|
if (active.value) {
|
||||||
|
fetchAndProcess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupIndexEffect(index());
|
||||||
|
// Series don't subscribe to onIndexChange - panes recreates them on index change
|
||||||
|
// onIndexChange.add(setupIndexEffect);
|
||||||
|
// _cleanup = () => onIndexChange.delete(setupIndexEffect);
|
||||||
|
|
||||||
|
addPriceScaleSelectorIfNeeded({
|
||||||
|
paneIndex,
|
||||||
|
seriesType,
|
||||||
|
unit,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return series;
|
||||||
}
|
}
|
||||||
|
|
||||||
const chart = {
|
const chart = {
|
||||||
@@ -738,8 +704,7 @@ export function createChart({
|
|||||||
* @param {string} args.name
|
* @param {string} args.name
|
||||||
* @param {Unit} args.unit
|
* @param {Unit} args.unit
|
||||||
* @param {number} args.order
|
* @param {number} args.order
|
||||||
* @param {AnyMetricPattern} [args.metric]
|
* @param {AnyMetricPattern} args.metric
|
||||||
* @param {Accessor<CandlestickData[]>} [args.data]
|
|
||||||
* @param {number} [args.paneIndex]
|
* @param {number} [args.paneIndex]
|
||||||
* @param {[Color, Color]} [args.colors] - [upColor, downColor] for legend
|
* @param {[Color, Color]} [args.colors] - [upColor, downColor] for legend
|
||||||
* @param {boolean} [args.defaultActive]
|
* @param {boolean} [args.defaultActive]
|
||||||
@@ -754,7 +719,6 @@ export function createChart({
|
|||||||
paneIndex = 0,
|
paneIndex = 0,
|
||||||
colors: customColors,
|
colors: customColors,
|
||||||
defaultActive,
|
defaultActive,
|
||||||
data,
|
|
||||||
inverse,
|
inverse,
|
||||||
options,
|
options,
|
||||||
}) {
|
}) {
|
||||||
@@ -828,7 +792,6 @@ export function createChart({
|
|||||||
paneIndex,
|
paneIndex,
|
||||||
seriesType: "Candlestick",
|
seriesType: "Candlestick",
|
||||||
unit,
|
unit,
|
||||||
data,
|
|
||||||
defaultActive,
|
defaultActive,
|
||||||
metric,
|
metric,
|
||||||
setOrder(order) {
|
setOrder(order) {
|
||||||
@@ -883,9 +846,8 @@ export function createChart({
|
|||||||
* @param {string} args.name
|
* @param {string} args.name
|
||||||
* @param {Unit} args.unit
|
* @param {Unit} args.unit
|
||||||
* @param {number} args.order
|
* @param {number} args.order
|
||||||
|
* @param {AnyMetricPattern} args.metric
|
||||||
* @param {Color | [Color, Color]} [args.color] - Single color or [positive, negative] colors
|
* @param {Color | [Color, Color]} [args.color] - Single color or [positive, negative] colors
|
||||||
* @param {AnyMetricPattern} [args.metric]
|
|
||||||
* @param {Accessor<HistogramData[]>} [args.data]
|
|
||||||
* @param {number} [args.paneIndex]
|
* @param {number} [args.paneIndex]
|
||||||
* @param {boolean} [args.defaultActive]
|
* @param {boolean} [args.defaultActive]
|
||||||
* @param {HistogramSeriesPartialOptions} [args.options]
|
* @param {HistogramSeriesPartialOptions} [args.options]
|
||||||
@@ -898,7 +860,6 @@ export function createChart({
|
|||||||
order,
|
order,
|
||||||
paneIndex = 0,
|
paneIndex = 0,
|
||||||
defaultActive,
|
defaultActive,
|
||||||
data,
|
|
||||||
options,
|
options,
|
||||||
}) {
|
}) {
|
||||||
const isDualColor = Array.isArray(color);
|
const isDualColor = Array.isArray(color);
|
||||||
@@ -937,7 +898,6 @@ export function createChart({
|
|||||||
paneIndex,
|
paneIndex,
|
||||||
seriesType: "Bar",
|
seriesType: "Bar",
|
||||||
unit,
|
unit,
|
||||||
data,
|
|
||||||
defaultActive,
|
defaultActive,
|
||||||
metric,
|
metric,
|
||||||
setOrder: (order) => iseries.setSeriesOrder(order),
|
setOrder: (order) => iseries.setSeriesOrder(order),
|
||||||
@@ -990,8 +950,7 @@ export function createChart({
|
|||||||
* @param {string} args.name
|
* @param {string} args.name
|
||||||
* @param {Unit} args.unit
|
* @param {Unit} args.unit
|
||||||
* @param {number} args.order
|
* @param {number} args.order
|
||||||
* @param {Accessor<LineData[]>} [args.data]
|
* @param {AnyMetricPattern} args.metric
|
||||||
* @param {AnyMetricPattern} [args.metric]
|
|
||||||
* @param {Color} [args.color]
|
* @param {Color} [args.color]
|
||||||
* @param {number} [args.paneIndex]
|
* @param {number} [args.paneIndex]
|
||||||
* @param {boolean} [args.defaultActive]
|
* @param {boolean} [args.defaultActive]
|
||||||
@@ -1005,7 +964,6 @@ export function createChart({
|
|||||||
color: _color,
|
color: _color,
|
||||||
paneIndex = 0,
|
paneIndex = 0,
|
||||||
defaultActive,
|
defaultActive,
|
||||||
data,
|
|
||||||
options,
|
options,
|
||||||
}) {
|
}) {
|
||||||
const color =
|
const color =
|
||||||
@@ -1044,7 +1002,6 @@ export function createChart({
|
|||||||
paneIndex,
|
paneIndex,
|
||||||
seriesType: "Line",
|
seriesType: "Line",
|
||||||
unit,
|
unit,
|
||||||
data,
|
|
||||||
defaultActive,
|
defaultActive,
|
||||||
metric,
|
metric,
|
||||||
setOrder: (order) => iseries.setSeriesOrder(order),
|
setOrder: (order) => iseries.setSeriesOrder(order),
|
||||||
@@ -1083,8 +1040,7 @@ export function createChart({
|
|||||||
* @param {string} args.name
|
* @param {string} args.name
|
||||||
* @param {Unit} args.unit
|
* @param {Unit} args.unit
|
||||||
* @param {number} args.order
|
* @param {number} args.order
|
||||||
* @param {Accessor<LineData[]>} [args.data]
|
* @param {AnyMetricPattern} args.metric
|
||||||
* @param {AnyMetricPattern} [args.metric]
|
|
||||||
* @param {Color} [args.color]
|
* @param {Color} [args.color]
|
||||||
* @param {number} [args.paneIndex]
|
* @param {number} [args.paneIndex]
|
||||||
* @param {boolean} [args.defaultActive]
|
* @param {boolean} [args.defaultActive]
|
||||||
@@ -1098,7 +1054,6 @@ export function createChart({
|
|||||||
color: _color,
|
color: _color,
|
||||||
paneIndex = 0,
|
paneIndex = 0,
|
||||||
defaultActive,
|
defaultActive,
|
||||||
data,
|
|
||||||
options,
|
options,
|
||||||
}) {
|
}) {
|
||||||
const color =
|
const color =
|
||||||
@@ -1150,7 +1105,6 @@ export function createChart({
|
|||||||
paneIndex,
|
paneIndex,
|
||||||
seriesType: "Line",
|
seriesType: "Line",
|
||||||
unit,
|
unit,
|
||||||
data,
|
|
||||||
defaultActive,
|
defaultActive,
|
||||||
metric,
|
metric,
|
||||||
setOrder: (order) => iseries.setSeriesOrder(order),
|
setOrder: (order) => iseries.setSeriesOrder(order),
|
||||||
@@ -1190,8 +1144,7 @@ export function createChart({
|
|||||||
* @param {string} args.name
|
* @param {string} args.name
|
||||||
* @param {Unit} args.unit
|
* @param {Unit} args.unit
|
||||||
* @param {number} args.order
|
* @param {number} args.order
|
||||||
* @param {Accessor<BaselineData[]>} [args.data]
|
* @param {AnyMetricPattern} args.metric
|
||||||
* @param {AnyMetricPattern} [args.metric]
|
|
||||||
* @param {number} [args.paneIndex]
|
* @param {number} [args.paneIndex]
|
||||||
* @param {boolean} [args.defaultActive]
|
* @param {boolean} [args.defaultActive]
|
||||||
* @param {Color} [args.topColor]
|
* @param {Color} [args.topColor]
|
||||||
@@ -1205,7 +1158,6 @@ export function createChart({
|
|||||||
order,
|
order,
|
||||||
paneIndex: _paneIndex,
|
paneIndex: _paneIndex,
|
||||||
defaultActive,
|
defaultActive,
|
||||||
data,
|
|
||||||
topColor = colors.green,
|
topColor = colors.green,
|
||||||
bottomColor = colors.red,
|
bottomColor = colors.red,
|
||||||
options,
|
options,
|
||||||
@@ -1254,7 +1206,6 @@ export function createChart({
|
|||||||
paneIndex,
|
paneIndex,
|
||||||
seriesType: "Baseline",
|
seriesType: "Baseline",
|
||||||
unit,
|
unit,
|
||||||
data,
|
|
||||||
defaultActive,
|
defaultActive,
|
||||||
metric,
|
metric,
|
||||||
setOrder: (order) => iseries.setSeriesOrder(order),
|
setOrder: (order) => iseries.setSeriesOrder(order),
|
||||||
@@ -1299,50 +1250,56 @@ export function createChart({
|
|||||||
blueprints.forEach((blueprint, order) => {
|
blueprints.forEach((blueprint, order) => {
|
||||||
if (blueprint.type === "Candlestick") {
|
if (blueprint.type === "Candlestick") {
|
||||||
chart.addCandlestickSeries({
|
chart.addCandlestickSeries({
|
||||||
|
metric: blueprint.metric,
|
||||||
name: blueprint.title,
|
name: blueprint.title,
|
||||||
unit,
|
unit,
|
||||||
data: blueprint.data,
|
colors: blueprint.colors,
|
||||||
defaultActive: blueprint.defaultActive,
|
defaultActive: blueprint.defaultActive,
|
||||||
paneIndex,
|
paneIndex,
|
||||||
|
options: blueprint.options,
|
||||||
order,
|
order,
|
||||||
});
|
});
|
||||||
} else if (blueprint.type === "Baseline") {
|
} else if (blueprint.type === "Baseline") {
|
||||||
chart.addBaselineSeries({
|
chart.addBaselineSeries({
|
||||||
|
metric: blueprint.metric,
|
||||||
name: blueprint.title,
|
name: blueprint.title,
|
||||||
unit,
|
unit,
|
||||||
data: blueprint.data,
|
|
||||||
defaultActive: blueprint.defaultActive,
|
defaultActive: blueprint.defaultActive,
|
||||||
paneIndex,
|
paneIndex,
|
||||||
|
options: blueprint.options,
|
||||||
order,
|
order,
|
||||||
});
|
});
|
||||||
} else if (blueprint.type === "Histogram") {
|
} else if (blueprint.type === "Histogram") {
|
||||||
chart.addHistogramSeries({
|
chart.addHistogramSeries({
|
||||||
|
metric: blueprint.metric,
|
||||||
name: blueprint.title,
|
name: blueprint.title,
|
||||||
unit,
|
unit,
|
||||||
color: blueprint.color,
|
color: blueprint.color,
|
||||||
data: blueprint.data,
|
|
||||||
defaultActive: blueprint.defaultActive,
|
defaultActive: blueprint.defaultActive,
|
||||||
paneIndex,
|
paneIndex,
|
||||||
|
options: blueprint.options,
|
||||||
order,
|
order,
|
||||||
});
|
});
|
||||||
} else if (blueprint.type === "Dots") {
|
} else if (blueprint.type === "Dots") {
|
||||||
chart.addDotsSeries({
|
chart.addDotsSeries({
|
||||||
|
metric: blueprint.metric,
|
||||||
name: blueprint.title,
|
name: blueprint.title,
|
||||||
unit,
|
unit,
|
||||||
color: blueprint.color,
|
color: blueprint.color,
|
||||||
data: blueprint.data,
|
|
||||||
defaultActive: blueprint.defaultActive,
|
defaultActive: blueprint.defaultActive,
|
||||||
paneIndex,
|
paneIndex,
|
||||||
|
options: blueprint.options,
|
||||||
order,
|
order,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
chart.addLineSeries({
|
chart.addLineSeries({
|
||||||
|
metric: blueprint.metric,
|
||||||
name: blueprint.title,
|
name: blueprint.title,
|
||||||
unit,
|
unit,
|
||||||
data: blueprint.data,
|
|
||||||
defaultActive: blueprint.defaultActive,
|
defaultActive: blueprint.defaultActive,
|
||||||
paneIndex,
|
paneIndex,
|
||||||
color: blueprint.color,
|
color: blueprint.color,
|
||||||
|
options: blueprint.options,
|
||||||
order,
|
order,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export function init({ option, brk }) {
|
|||||||
|
|
||||||
const chart = createChart({
|
const chart = createChart({
|
||||||
parent: chartElement,
|
parent: chartElement,
|
||||||
signals,
|
|
||||||
id: "charts",
|
id: "charts",
|
||||||
brk,
|
brk,
|
||||||
captureElement: chartElement,
|
captureElement: chartElement,
|
||||||
@@ -223,6 +222,8 @@ export function init({ option, brk }) {
|
|||||||
orderStart,
|
orderStart,
|
||||||
legend,
|
legend,
|
||||||
}) {
|
}) {
|
||||||
|
console.log("createSeriesFromBlueprints paneIndex:", paneIndex);
|
||||||
|
|
||||||
legend.removeFrom(orderStart);
|
legend.removeFrom(orderStart);
|
||||||
seriesList.splice(orderStart).forEach((series) => series.remove());
|
seriesList.splice(orderStart).forEach((series) => series.remove());
|
||||||
|
|
||||||
@@ -321,10 +322,7 @@ export function init({ option, brk }) {
|
|||||||
signals.createScopedEffect(
|
signals.createScopedEffect(
|
||||||
() => ({ unit: topUnit(), _: indexVersion() }),
|
() => ({ unit: topUnit(), _: indexVersion() }),
|
||||||
({ unit }) => {
|
({ unit }) => {
|
||||||
// Remove old series BEFORE creating new one
|
// Create price series BEFORE removing old one to prevent pane collapse
|
||||||
seriesListTop[0]?.remove();
|
|
||||||
|
|
||||||
// Create price series
|
|
||||||
/** @type {AnySeries | undefined} */
|
/** @type {AnySeries | undefined} */
|
||||||
let series;
|
let series;
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
@@ -350,6 +348,7 @@ export function init({ option, brk }) {
|
|||||||
}
|
}
|
||||||
if (!series) throw Error("Unreachable");
|
if (!series) throw Error("Unreachable");
|
||||||
|
|
||||||
|
seriesListTop[0]?.remove();
|
||||||
seriesListTop[0] = series;
|
seriesListTop[0] = series;
|
||||||
|
|
||||||
// Live price update effect
|
// Live price update effect
|
||||||
@@ -456,7 +455,7 @@ function createIndexSelector(option, chart) {
|
|||||||
// Use preferred index if available, otherwise fall back to first choice
|
// Use preferred index if available, otherwise fall back to first choice
|
||||||
let currentValue = newChoices.includes(preferredIndex)
|
let currentValue = newChoices.includes(preferredIndex)
|
||||||
? preferredIndex
|
? preferredIndex
|
||||||
: newChoices[0] ?? "date";
|
: (newChoices[0] ?? "date");
|
||||||
|
|
||||||
if (currentValue !== chart.indexName.value) {
|
if (currentValue !== chart.indexName.value) {
|
||||||
chart.indexName.set(currentValue);
|
chart.indexName.set(currentValue);
|
||||||
|
|||||||
Reference in New Issue
Block a user