diff --git a/website/packages/lightweight-charts/types.d.ts b/website/packages/lightweight-charts/types.d.ts index a15bd5132..4da1bc23b 100644 --- a/website/packages/lightweight-charts/types.d.ts +++ b/website/packages/lightweight-charts/types.d.ts @@ -12,7 +12,7 @@ import { ISeriesApi, BaselineData, } from "./v4.2.0/types"; -import { Color } from "../../scripts/types/self"; +import { Color, ValuedCandlestickData } from "../../scripts/types/self"; interface BaseSeriesBlueprint { title: string; @@ -69,11 +69,13 @@ interface BaseSeries { } interface SingleSeries extends BaseSeries { iseries: ISeriesApi; + dataset: Accessor<(SingleValueData | ValuedCandlestickData)[] | null>; } interface SplitSeries extends BaseSeries { chunks: Array | undefined>>; dataset: ResourceDataset; } +type AnySeries = SingleSeries | SplitSeries; interface CreateSingleSeriesParameters { blueprint: SingleSeriesBlueprint; @@ -85,7 +87,6 @@ interface CreateSplitSeriesParameters { blueprint: SplitSeriesBlueprint; id: string; index: number; - setMinMaxMarkersWhenIdle: VoidFunction; disabled?: Accessor; } @@ -98,6 +99,8 @@ type ChartPane = IChartApi & { createSplitSeries: ( a: CreateSplitSeriesParameters, ) => SplitSeries; + anySeries: AnySeries[]; + singleSeries: SingleSeries[]; splitSeries: SplitSeries[]; }; @@ -118,5 +121,5 @@ interface Marker { interface HoveredLegend { label: HTMLLabelElement; - series: SingleSeries | SplitSeries; + series: AnySeries; } diff --git a/website/packages/lightweight-charts/wrapper.js b/website/packages/lightweight-charts/wrapper.js index 1a6722496..9e55d6bcd 100644 --- a/website/packages/lightweight-charts/wrapper.js +++ b/website/packages/lightweight-charts/wrapper.js @@ -393,10 +393,10 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => { if (urlFrom && urlTo) { if (scale === "date" && urlFrom.includes("-") && urlTo.includes("-")) { - console.log({ - from: new Date(urlFrom).toJSON().split("T")[0], - to: new Date(urlTo).toJSON().split("T")[0], - }); + // console.log({ + // from: new Date(urlFrom).toJSON().split("T")[0], + // to: new Date(urlTo).toJSON().split("T")[0], + // }); return { from: new Date(urlFrom).toJSON().split("T")[0], to: new Date(urlTo).toJSON().split("T")[0], @@ -405,10 +405,10 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => { scale === "height" && (!urlFrom.includes("-") || !urlTo.includes("-")) ) { - console.log({ - from: Number(urlFrom), - to: Number(urlTo), - }); + // console.log({ + // from: Number(urlFrom), + // to: Number(urlTo), + // }); return { from: Number(urlFrom), to: Number(urlTo), @@ -426,7 +426,7 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => { const savedTimeRange = getSavedTimeRange(); - console.log(savedTimeRange); + // console.log(savedTimeRange); if (savedTimeRange) { return savedTimeRange; @@ -527,7 +527,7 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => { const notHoveredLegendTransparency = "66"; /** * @param {Object} args - * @param {SingleSeries | SplitSeries} args.series + * @param {AnySeries} args.series * @param {string} [args.extraName] */ function createLegend({ series, extraName }) { @@ -694,7 +694,7 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => { } createHoverEffect(); - if ("dataset" in series) { + if ("dataset" in series && "url" in series.dataset) { const anchor = window.document.createElement("a"); anchor.href = series.dataset.url; anchor.target = "_blank"; @@ -730,9 +730,6 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => { chartDiv.classList.add("lightweight-chart"); chartWrapper.append(chartDiv); - /** @type {SplitSeries[]} */ - const paneSplitSeries = []; - options = { ...options }; if (kind === "static") { options.handleScale = false; @@ -928,15 +925,17 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => { utils.url.removeParam(paramKey); utils.storage.remove(storageKey); } + + setMinMaxMarkersWhenIdle(); }, ); return series; } - const chartPane = /** @type {ChartPane} */ (_chart); + const pane = /** @type {ChartPane} */ (_chart); - chartPane.createSingleSeries = function ({ blueprint, id }) { + pane.createSingleSeries = function ({ blueprint, id }) { /** @type {ISeriesApi} */ let s; @@ -965,6 +964,7 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => { defaultActive: blueprint.defaultActive, }), iseries: s, + dataset: blueprint.data || (() => /** @type {any} */ ([])), }; signals.createEffect(series.visible, (visible) => { @@ -975,13 +975,15 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => { createLegend({ series }); + pane.singleSeries.push(series); + pane.anySeries.push(series); + return series; }; - chartPane.createSplitSeries = function ({ + pane.createSplitSeries = function ({ id, index: seriesIndex, disabled, - setMinMaxMarkersWhenIdle, dataset, blueprint, }) { @@ -998,7 +1000,8 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => { chunks: new Array(dataset.fetchedJSONs.length), }; - paneSplitSeries.push(series); + pane.splitSeries.push(series); + pane.anySeries.push(series); chartSplitSeries.unshift(series); dataset.fetchedJSONs.forEach((json, index) => { @@ -1011,7 +1014,7 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => { const isMyTurn = signals.createMemo(() => { if (seriesIndex <= 0) return true; - const previousSeriesChunk = paneSplitSeries.at(seriesIndex - 1) + const previousSeriesChunk = pane.splitSeries.at(seriesIndex - 1) ?.chunks[index]; const isPreviousSeriesOnChart = previousSeriesChunk?.(); @@ -1139,30 +1142,38 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => { return series; }; - chartPane.hidden = () => { + pane.hidden = () => { return chartWrapper.hidden; }; - chartPane.setHidden = (b) => { + pane.setHidden = (b) => { chartWrapper.hidden = b; }; - chartPane.splitSeries = paneSplitSeries; - chartPane.setInitialVisibleTimeRange = () => { + pane.splitSeries = []; + pane.singleSeries = []; + pane.anySeries = []; + pane.setInitialVisibleTimeRange = function () { const range = visibleTimeRange(); if (range) { - chartPane.timeScale().setVisibleRange(/** @type {any} */ (range)); + pane.timeScale().setVisibleRange(/** @type {any} */ (range)); // On small screen it doesn't it might not set it in time setTimeout(() => { try { - chartPane.timeScale().setVisibleRange(/** @type {any} */ (range)); + pane.timeScale().setVisibleRange(/** @type {any} */ (range)); } catch {} }, 50); } }; + pane.remove = function () { + _chart.remove(); + pane.splitSeries.length = 0; + pane.singleSeries.length = 0; + pane.anySeries.length = 0; + }; if (whitespace) { - chartPane.whitespace = setWhitespace({ chart: _chart, scale, utils }); + pane.whitespace = setWhitespace({ chart: _chart, scale, utils }); } function createUnitAndModeElements() { @@ -1206,23 +1217,32 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => { switch (kind) { case "static": { config?.forEach((params) => { - chartPane.createSingleSeries({ + pane.createSingleSeries({ id: utils.stringToId(params.title), blueprint: params, }); }); - chartPane.timeScale().fitContent(); + pane.timeScale().fitContent(); + + if (!paneIndex) { + setTimeout(() => { + pane.timeScale().subscribeVisibleTimeRangeChange((range) => { + if (!range) return; + visibleTimeRange.set(range); + }); + }); + } break; } case "moveable": { - chartPane.setInitialVisibleTimeRange(); + pane.setInitialVisibleTimeRange(); updateVisibleDatasetIds(); if (!paneIndex) { setTimeout(() => { - chartPane.timeScale().subscribeVisibleTimeRangeChange((range) => { + pane.timeScale().subscribeVisibleTimeRangeChange((range) => { if (!range) return; visibleTimeRange.set(range); debouncedUpdateVisibleDatasetIds(); @@ -1235,9 +1255,231 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => { } } - panes.push(chartPane); + panes.push(pane); - return chartPane; + pane.subscribeCrosshairMove(({ time, sourceEvent }) => { + // Don't override crosshair position from scroll event + if (time && !sourceEvent) return; + + for ( + let otherChartIndex = 0; + otherChartIndex < panes.length; + otherChartIndex++ + ) { + const otherPane = panes[otherChartIndex]; + + if (otherPane && paneIndex !== otherChartIndex) { + if (time) { + otherPane.setCrosshairPosition(NaN, time, otherPane.whitespace); + } else { + // No time when mouse goes outside the chart + otherPane.clearCrosshairPosition(); + } + } + } + }); + + const chartVisible = signals.createMemo(() => + pane.anySeries.some((series) => series.visible()), + ); + + function createChartVisibilityEffect() { + signals.createEffect(chartVisible, (chartVisible) => { + pane.setHidden(!chartVisible); + }); + } + createChartVisibilityEffect(); + + function createTimeScaleVisibilityEffect() { + signals.createEffect(chartVisible, (chartVisible) => { + let i = paneIndex || 0; + const last = i === panes.length - 1; + const visible = last && chartVisible; + + pane.timeScale().applyOptions({ + visible, + }); + + if (i > 0 && last) { + panes.slice(0, -1).forEach((pane) => + pane.timeScale().applyOptions({ + visible: !visible, + }), + ); + } + }); + } + createTimeScaleVisibilityEffect(); + + pane.timeScale().subscribeVisibleLogicalRangeChange((logicalRange) => { + if (!logicalRange) return; + for (let i = 0; i < panes.length; i++) { + if (paneIndex !== i) { + panes[i].timeScale().setVisibleLogicalRange(logicalRange); + } + } + }); + + function setMinMaxMarkers() { + try { + const { from, to } = visibleTimeRange(); + + const dateFrom = new Date(String(from)); + const dateTo = new Date(String(to)); + + let max = /** @type {Marker | undefined} */ (undefined); + let min = /** @type {Marker | undefined} */ (undefined); + + /** @type {(SeriesMarker