From 8f5f28ede659223483f6b10dd253599c00ae9b97 Mon Sep 17 00:00:00 2001 From: k Date: Wed, 24 Jul 2024 00:05:18 +0200 Subject: [PATCH] app: charts: add unit and price mode switch --- CHANGELOG.md | 2 + app/package.json | 2 +- app/pnpm-lock.yaml | 14 +- .../frames/chart/components/chart.tsx | 425 +++++++++-- .../frames/chart/components/charts.tsx | 137 ++++ .../frames/chart/components/timeScale.tsx | 68 +- app/src/app/components/frames/chart/index.tsx | 44 +- app/src/app/components/frames/settings.tsx | 81 ++- app/src/app/types.d.ts | 2 + app/src/scripts/lightweightCharts/create.ts | 22 +- app/src/scripts/lightweightCharts/group.ts | 209 ++++++ app/src/scripts/lightweightCharts/price.ts | 48 ++ app/src/scripts/presets/addresses/index.ts | 7 +- app/src/scripts/presets/apply.ts | 669 ------------------ app/src/scripts/presets/blocks/index.ts | 23 +- app/src/scripts/presets/cointime/index.ts | 60 +- app/src/scripts/presets/hodlers/index.ts | 1 + .../scripts/presets/market/averages/index.ts | 7 +- app/src/scripts/presets/market/index.ts | 2 + .../scripts/presets/market/returns/index.ts | 3 +- app/src/scripts/presets/miners/index.ts | 151 ++-- .../presets/templates/cohort/pricesPaid.ts | 3 + .../presets/templates/cohort/realized.ts | 15 + .../presets/templates/cohort/supply.ts | 12 +- .../presets/templates/cohort/unrealized.ts | 5 + .../scripts/presets/templates/cohort/utxo.ts | 1 + app/src/scripts/presets/templates/ratio.ts | 21 +- app/src/scripts/presets/templates/recap.ts | 12 + app/src/scripts/presets/transactions/index.ts | 10 +- app/src/scripts/presets/types.d.ts | 29 +- .../utils/selectableList/static/index.ts | 6 +- app/src/solid/idle.ts | 26 + app/src/types/lightweight-charts.d.ts | 1 + server/.github/workflows/rust.yml | 22 - 34 files changed, 1153 insertions(+), 987 deletions(-) create mode 100644 app/src/app/components/frames/chart/components/charts.tsx create mode 100644 app/src/scripts/lightweightCharts/group.ts create mode 100644 app/src/scripts/lightweightCharts/price.ts delete mode 100644 app/src/scripts/presets/apply.ts create mode 100644 app/src/solid/idle.ts delete mode 100644 server/.github/workflows/rust.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index e736d445e..2060c270a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,8 @@ - General - Added chart scroll button for nice animations à la Wicked + - Added scale mode switch (Linear/Logarithmic) at the bottom right of all charts + - Added unit at the top left of all charts - Added a backup API in case the main one fails or is offline - Complete redesign of the datasets object - Removed import of routes in JSON in favor for hardcoded typed routes in string format which resulted in: diff --git a/app/package.json b/app/package.json index 90ccf64aa..b7f1b0411 100644 --- a/app/package.json +++ b/app/package.json @@ -35,7 +35,7 @@ "pwa-asset-generator": "^6.3.1", "rollup-plugin-visualizer": "^5.12.0", "tailwindcss": "^3.4.6", - "typescript": "^5.5.3", + "typescript": "^5.5.4", "unplugin-auto-import": "^0.18.0", "unplugin-icons": "^0.19.0", "vite": "^5.3.4", diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index 6bc5deb83..1bb2b5e3f 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -59,8 +59,8 @@ devDependencies: specifier: ^3.4.6 version: 3.4.6 typescript: - specifier: ^5.5.3 - version: 5.5.3 + specifier: ^5.5.4 + version: 5.5.4 unplugin-auto-import: specifier: ^0.18.0 version: 0.18.0(rollup@2.79.1) @@ -2635,7 +2635,7 @@ packages: hasBin: true dependencies: caniuse-lite: 1.0.30001643 - electron-to-chromium: 1.4.832 + electron-to-chromium: 1.5.0 node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.2) dev: true @@ -3101,8 +3101,8 @@ packages: jake: 10.9.2 dev: true - /electron-to-chromium@1.4.832: - resolution: {integrity: sha512-cTen3SB0H2SGU7x467NRe1eVcQgcuS6jckKfWJHia2eo0cHIGOqHoAxevIYZD4eRHcWjkvFzo93bi3vJ9W+1lA==} + /electron-to-chromium@1.5.0: + resolution: {integrity: sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA==} dev: true /emoji-regex@8.0.0: @@ -5651,8 +5651,8 @@ packages: possible-typed-array-names: 1.0.0 dev: true - /typescript@5.5.3: - resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} + /typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} engines: {node: '>=14.17'} hasBin: true dev: true diff --git a/app/src/app/components/frames/chart/components/chart.tsx b/app/src/app/components/frames/chart/components/chart.tsx index c6fcdb9e5..cacdf7669 100644 --- a/app/src/app/components/frames/chart/components/chart.tsx +++ b/app/src/app/components/frames/chart/components/chart.tsx @@ -1,82 +1,369 @@ import { requestIdleCallbackPossible } from "/src/env"; -import { applySeriesList } from "/src/scripts/presets/apply"; +import { chunkIdToIndex } from "/src/scripts/datasets/resource"; +import { createChart } from "/src/scripts/lightweightCharts/create"; +import { createSeriesGroup } from "/src/scripts/lightweightCharts/group"; +import { setMinMaxMarkers } from "/src/scripts/lightweightCharts/markers"; +import { + debouncedUpdateVisiblePriceSeriesType, + updateVisiblePriceSeriesType, +} from "/src/scripts/lightweightCharts/price"; +import { + initTimeScale, + setInitialTimeRange, +} from "/src/scripts/lightweightCharts/time"; +import { setWhitespace } from "/src/scripts/lightweightCharts/whitespace"; +import { SeriesType } from "/src/scripts/presets/enums"; +import { colors } from "/src/scripts/utils/colors"; +import { debounce } from "/src/scripts/utils/debounce"; +import { createSL } from "/src/scripts/utils/selectableList/static"; +import { webSockets } from "/src/scripts/ws"; +import { classPropToString } from "/src/solid/classes"; import { createRWS } from "/src/solid/rws"; +import { RadioGroup } from "../../settings"; + export function Chart({ - charts, - parentDiv, - presets, - datasets, - legendSetter, - dark, + activeDatasets, activeIds, + charts, + chartsDrawn, + dark, + datasets, + exactRange, + firstChartSetter, + index, + lastActiveIndex, + lastChartIndex, + legendSetter, + preset: presetAccessor, + priceSeriesType, + seriesConfigs, + seriesCount, }: { - charts: RWS; - parentDiv: RWS; - presets: Presets; - datasets: Datasets; - legendSetter: Setter; - dark: Accessor; + activeDatasets: ReadWriteSignal[]>; activeIds: RWS; + charts: ReadWriteSignal< + { + chart: RWS; + whitespace: RWS; + }[] + >; + chartsDrawn: Accessor[]>; + dark: Accessor; + datasets: Datasets; + exactRange: ReadWriteSignal; + firstChartSetter: Setter; + index: Accessor; + lastActiveIndex: Accessor; + lastChartIndex: Accessor; + legendSetter: Setter; + preset: Accessor; + priceSeriesType: ReadWriteSignal; + seriesConfigs: SeriesConfig[]; + seriesCount: Accessor; }) { - const wasIdle = createRWS(false); + const div = createRWS(undefined); + const chartIndex = index(); - if (requestIdleCallbackPossible) { - const idleCallback = requestIdleCallback(() => { - wasIdle.set(true); - }); + const isDrawn = chartsDrawn()[chartIndex]; + const isLastDrawn = createMemo( + () => chartsDrawn().findLastIndex((drawn) => drawn()) === chartIndex, + ); - onCleanup(() => { - cancelIdleCallback(idleCallback); - }); - } else { - const timeout = setTimeout(() => { - wasIdle.set(true); - }, 500); - - onCleanup(() => { - clearTimeout(timeout); - }); - } - - onMount(() => { - createEffect(() => { - const preset = presets.selected(); - const div = parentDiv(); - - if (!wasIdle() || !div) return; - - untrack(() => { - try { - console.log(`preset: ${preset.id}`); - applySeriesList({ - charts, - parentDiv: div, - datasets, - preset, - legendSetter, - dark, - activeIds, - priceScaleOptions: preset.priceScaleOptions, - top: preset.top, - bottom: preset.bottom, - }); - } catch (error) { - console.error("chart: render: failed", error); - } - }); - }); - - onCleanup(() => - charts.set((charts) => { - charts.forEach((chart) => { - chart.remove(); - }); - - return []; - }), - ); + const chartPriceModeKey = `chart-price-mode-${chartIndex}` as const; + const chartPriceMode = createSL(["Linear", "Log"] as const, { + saveable: { + key: chartPriceModeKey, + mode: "localStorage", + }, + defaultValue: chartIndex === 0 ? "Log" : "Linear", }); - return <>; + createEffect( + on([div, () => charts()[chartIndex]], ([div, chartConfig]) => { + console.log({ + div, + chartConfig, + }); + + if (!div || !chartConfig) return; + + const preset = presetAccessor(); + const scale = preset.scale; + + const chart = createChart({ + scale, + element: div, + dark, + }); + + if (!chart) { + console.log("chart: undefined"); + return; + } + + const whitespace = setWhitespace(chart, scale); + + batch(() => { + chartConfig.chart.set(chart); + chartConfig.whitespace.set(whitespace); + + if (chartIndex === 0) { + firstChartSetter(chart); + } + }); + + const range = exactRange(); + + setInitialTimeRange({ chart, range }); + + if (chartIndex === 0) { + initTimeScale({ + scale, + chart, + activeIds, + exactRange, + }); + + if (range) { + updateVisiblePriceSeriesType({ + scale, + chart, + priceSeriesType, + timeRange: range, + }); + } + } + + const chartLegend: SeriesLegend[] = []; + + onCleanup(() => { + chartLegend.length = 0; + }); + + const markerCallback = () => + setMinMaxMarkers({ + scale, + visibleRange: exactRange(), + legendList: chartLegend, + dark, + activeIds: activeIds, + }); + + const debouncedSetMinMaxMarkers = requestIdleCallbackPossible + ? () => requestIdleCallback(markerCallback) + : debounce( + markerCallback, + seriesCount() * 10 + scale === "date" ? 50 : 100, + ); + + createEffect(on([exactRange, dark], debouncedSetMinMaxMarkers)); + + if (chartIndex === 0) { + const datasetPath: AnyDatasetPath = `/${scale}-to-price`; + + const dataset = datasets.getOrImport(scale, datasetPath); + + // Don't trigger reactivity by design + activeDatasets().push(dataset); + + const title = "Price"; + + function createPriceSeries(seriesType: PriceSeriesType) { + let seriesConfig: SeriesConfig; + + if (seriesType === "Candlestick") { + seriesConfig = { + datasetPath, + title, + seriesType: SeriesType.Candlestick, + }; + } else { + seriesConfig = { + datasetPath, + title, + color: colors.white, + }; + } + + const priceSeries = createSeriesGroup({ + scale, + datasets, + index: -1, + activeIds, + seriesConfig, + chart, + chartLegend, + lastActiveIndex, + preset, + disabled: () => priceSeriesType() !== seriesType, + debouncedSetMinMaxMarkers, + dark, + }); + + createEffect(() => { + const latest = webSockets.liveKrakenCandle.latest(); + + if (!latest) return; + + const index = chunkIdToIndex(scale, latest.year); + + const series = priceSeries.seriesList.at(index)?.(); + + series?.update(latest); + }); + + return priceSeries; + } + + const priceCandlestickLegend = createPriceSeries("Candlestick"); + const priceLineLegend = createPriceSeries("Line"); + + createEffect(() => { + priceCandlestickLegend.visible.set(priceLineLegend.visible()); + }); + + createEffect(() => { + priceLineLegend.visible.set(priceCandlestickLegend.visible()); + }); + } + + [...seriesConfigs].reverse().forEach((seriesConfig, index) => { + const dataset = datasets.getOrImport(scale, seriesConfig.datasetPath); + + // Don't trigger reactivity by design + activeDatasets().push(dataset); + + createSeriesGroup({ + scale, + datasets, + activeIds, + index, + seriesConfig, + chartLegend, + chart, + preset, + lastActiveIndex, + debouncedSetMinMaxMarkers, + dark, + }); + }); + + chartLegend.forEach((legend) => { + createEffect(on(legend.visible, debouncedSetMinMaxMarkers)); + }); + + legendSetter((l) => { + for (let i = 0; i < chartLegend.length; i++) { + l.splice(0, 0, chartLegend[i]); + } + return l; + }); + + createEffect(() => + isDrawn.set(() => chartLegend.some((legend) => legend.drawn())), + ); + + createEffect(() => + chart.timeScale().applyOptions({ + visible: isLastDrawn(), + }), + ); + + createEffect(() => + chart.priceScale("right").applyOptions({ + mode: chartPriceMode.selected() === "Linear" ? 0 : 1, + }), + ); + + chart.timeScale().subscribeVisibleLogicalRangeChange((logicalRange) => { + if (!logicalRange) return; + + // Must be the chart with the visible timeScale + if (chartIndex === lastChartIndex()) { + debouncedUpdateVisiblePriceSeriesType({ + scale, + chart, + logicalRange, + priceSeriesType, + }); + } + + for ( + let otherChartIndex = 0; + otherChartIndex <= lastChartIndex(); + otherChartIndex++ + ) { + if (chartIndex !== otherChartIndex) { + const chart = charts()[otherChartIndex].chart(); + + chart?.timeScale().setVisibleLogicalRange(logicalRange); + } + } + }); + + chart.subscribeCrosshairMove(({ time, sourceEvent }) => { + // Don't override crosshair position from scroll event + if (time && !sourceEvent) return; + + for ( + let otherChartIndex = 0; + otherChartIndex <= lastChartIndex(); + otherChartIndex++ + ) { + const { whitespace: _whitespace, chart: _otherChart } = + charts()[otherChartIndex]; + + const otherChart = _otherChart(); + const whitespace = _whitespace(); + + if (otherChart && whitespace && chartIndex !== otherChartIndex) { + if (time) { + otherChart.setCrosshairPosition(NaN, time, whitespace); + } else { + // No time when mouse goes outside the chart + otherChart.clearCrosshairPosition(); + } + } + } + }); + + // Trigger reactivity now + activeDatasets.set((l) => l); + }), + ); + + return ( +
+
+ + +
+ {chartIndex === 0 + ? ("US Dollars" satisfies Unit) + : presetAccessor().unit} +
+
+ +
+
+
+ ); } diff --git a/app/src/app/components/frames/chart/components/charts.tsx b/app/src/app/components/frames/chart/components/charts.tsx new file mode 100644 index 000000000..a3dde2d3d --- /dev/null +++ b/app/src/app/components/frames/chart/components/charts.tsx @@ -0,0 +1,137 @@ +import { chunkIdToIndex } from "/src/scripts/datasets/resource"; +import { + getInitialTimeRange, + setActiveIds, +} from "/src/scripts/lightweightCharts/time"; +import { createRWS } from "/src/solid/rws"; + +import { Chart } from "./chart"; + +export function Charts({ + firstChartSetter, + preset, + datasets, + legendSetter, + dark, + activeIds, +}: { + firstChartSetter: Setter; + preset: Accessor; + datasets: Datasets; + legendSetter: Setter; + dark: Accessor; + activeIds: RWS; +}) { + const scale = createMemo(() => preset().scale); + const exactRange = createRWS(getInitialTimeRange(scale())); + const priceSeriesType = createRWS("Candlestick"); + const activeDatasets = createRWS([] as ResourceDataset[], { + equals: false, + }); + const chartSeriesConfigs = createRWS([] as SeriesConfig[][], { + equals: false, + }); + const charts = createRWS( + [] as { + chart: RWS; + whitespace: RWS; + }[], + { + equals: false, + }, + ); + const lastChartIndex = createMemo(() => chartSeriesConfigs().length - 1); + const seriesCount = createMemo(() => + chartSeriesConfigs().reduce( + (acc, l) => (acc += l.length), + 1, // Because of price series + ), + ); + const lastActiveIndex = createMemo(() => { + const last = activeIds().at(-1); + return last !== undefined + ? chunkIdToIndex(preset().scale, last) + : undefined; + }); + const chartsDrawn = createMemo(() => + chartSeriesConfigs().map((_) => createRWS(true)), + ); + + createEffect( + on([activeIds, activeDatasets], ([ids, activeDatasets]) => { + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + for (let j = 0; j < activeDatasets.length; j++) { + activeDatasets[j].fetch(id); + } + } + }), + ); + + createEffect( + on(preset, (preset) => { + const scale = preset.scale; + + exactRange.set(getInitialTimeRange(scale)); + + chartSeriesConfigs.set( + [preset.top || [], preset.bottom].flatMap((list) => + list ? [list] : [], + ), + ); + + charts.set(() => + new Array(chartSeriesConfigs().length).fill(undefined).map(() => ({ + chart: createRWS(undefined as IChartApi | undefined), + whitespace: createRWS(undefined as ISeriesApiAny | undefined), + })), + ); + + setActiveIds({ + exactRange: exactRange(), + activeIds, + }); + + legendSetter(() => []); + }), + ); + + onCleanup(() => { + firstChartSetter(undefined); + + charts().map(({ chart, whitespace }) => { + chart()?.remove(); + chart.set(undefined); + whitespace.set(undefined); + }); + }); + + return ( + index === 0 || configs.length !== 0, + )} + > + {(seriesConfigs, index) => ( + + )} + + ); +} diff --git a/app/src/app/components/frames/chart/components/timeScale.tsx b/app/src/app/components/frames/chart/components/timeScale.tsx index 72a116953..4fa64eb90 100644 --- a/app/src/app/components/frames/chart/components/timeScale.tsx +++ b/app/src/app/components/frames/chart/components/timeScale.tsx @@ -6,26 +6,26 @@ import { GENESIS_DAY } from "../../../../../scripts/lightweightCharts/whitespace import { Box } from "../../box"; import { Scrollable } from "../../scrollable"; -const MULTIPLIER = 0.0025; -const DELAY = 25; +const DELAY = 1; +const MULTIPLIER = DELAY / 1000; const LEFT = -1; const RIGHT = 1; export function TimeScale({ scale, - charts, + firstChart, }: { scale: Accessor; - charts: RWS; + firstChart: RWS; }) { const today = new Date(); - const disabled = createMemo(() => charts().length === 0); + const disabled = createMemo(() => !firstChart()); const scrollDirection = createRWS(0); const timeScale = createMemo(() => { - const chart = charts().at(0); + const chart = firstChart(); if (!chart) return undefined; return chart.timeScale(); }); @@ -86,21 +86,25 @@ export function TimeScale({ @@ -108,7 +112,7 @@ export function TimeScale({ minWidth disabled={disabled} onClick={() => - setTimeScale({ scale: scale(), charts, days: 3 * 30 }) + setTimeScale({ scale: scale(), timeScale, days: 3 * 30 }) } > 3 Months @@ -117,7 +121,7 @@ export function TimeScale({ minWidth disabled={disabled} onClick={() => - setTimeScale({ scale: scale(), charts, days: 6 * 30 }) + setTimeScale({ scale: scale(), timeScale, days: 6 * 30 }) } > 6 Months @@ -128,7 +132,7 @@ export function TimeScale({ onClick={() => setTimeScale({ scale: scale(), - charts, + timeScale, days: Math.ceil( (today.getTime() - new Date(`${today.getUTCFullYear()}-01-01`).getTime()) / @@ -143,7 +147,7 @@ export function TimeScale({ minWidth disabled={disabled} onClick={() => - setTimeScale({ scale: scale(), charts, days: 365 }) + setTimeScale({ scale: scale(), timeScale, days: 365 }) } > 1 Year @@ -152,7 +156,7 @@ export function TimeScale({ minWidth disabled={disabled} onClick={() => - setTimeScale({ scale: scale(), charts, days: 2 * 365 }) + setTimeScale({ scale: scale(), timeScale, days: 2 * 365 }) } > 2 Years @@ -161,7 +165,7 @@ export function TimeScale({ minWidth disabled={disabled} onClick={() => - setTimeScale({ scale: scale(), charts, days: 4 * 365 }) + setTimeScale({ scale: scale(), timeScale, days: 4 * 365 }) } > 4 Years @@ -170,7 +174,7 @@ export function TimeScale({ minWidth disabled={disabled} onClick={() => - setTimeScale({ scale: scale(), charts, days: 8 * 365 }) + setTimeScale({ scale: scale(), timeScale, days: 8 * 365 }) } > 8 Years @@ -187,7 +191,9 @@ export function TimeScale({ @@ -214,7 +220,7 @@ export function TimeScale({ onClick={() => setTimeScale({ scale: scale(), - charts, + timeScale, range: { from: i * 100_000, to: (i + 0.5) * 100_000, @@ -282,13 +288,13 @@ function Button({ } function setTimeScale({ - charts, + timeScale, scale, days, year, range, }: { - charts: RWS; + timeScale: Accessor | undefined>; scale: ResourceScale; days?: number; year?: number; @@ -307,22 +313,16 @@ function setTimeScale({ from = new Date(GENESIS_DAY); } - charts() - .at(0) - ?.timeScale() - .setVisibleRange({ - from: (from.getTime() / 1000) as Time, - to: (to.getTime() / 1000) as Time, - }); + timeScale()?.setVisibleRange({ + from: (from.getTime() / 1000) as Time, + to: (to.getTime() / 1000) as Time, + }); } else if (scale === "height") { if (range) { - charts() - .at(0) - ?.timeScale() - .setVisibleRange({ - from: range.from as Time, - to: range.to as Time, - }); + timeScale()?.setVisibleRange({ + from: range.from as Time, + to: range.to as Time, + }); } } } diff --git a/app/src/app/components/frames/chart/index.tsx b/app/src/app/components/frames/chart/index.tsx index 5a2ee0e04..a0d318273 100644 --- a/app/src/app/components/frames/chart/index.tsx +++ b/app/src/app/components/frames/chart/index.tsx @@ -1,4 +1,5 @@ import { classPropToString } from "/src/solid/classes"; +import { createWasIdleAccessor } from "/src/solid/idle"; import { createRWS } from "/src/solid/rws"; import { Box } from "../box"; @@ -24,18 +25,18 @@ export function ChartFrame({ dark: Accessor; standalone: boolean; }) { - const legend = createRWS([]); + const legend = createRWS([], { equals: false }); - const charts = createRWS([]); - - const div = createRWS(undefined); + const firstChart = createRWS(undefined); const scale = createMemo(() => presets.selected().scale); const activeIds = createRWS([] as number[], { equals: false }); - const Chart = lazy(() => - import("./components/chart").then((d) => ({ default: d.Chart })), + const wasIdle = createWasIdleAccessor(); + + const Charts = lazy(() => + import("./components/charts").then((d) => ({ default: d.Charts })), ); return ( @@ -49,7 +50,13 @@ export function ChartFrame({ display: (hide ? hide() : false) ? "none" : undefined, }} > - + <div class="border-lighter border-t" /> @@ -68,19 +75,20 @@ export function ChartFrame({ </div> </Box> - <div ref={div.set} class="-mr-2 -mt-2 flex min-h-0 flex-1 flex-col"> - <Chart - parentDiv={div} - charts={charts} - datasets={datasets} - legendSetter={legend.set} - presets={presets} - dark={dark} - activeIds={activeIds} - /> + <div class="-mr-2 -mt-2 flex min-h-0 flex-1 flex-col"> + <Show when={wasIdle()}> + <Charts + firstChartSetter={firstChart.set} + datasets={datasets} + legendSetter={legend.set} + preset={presets.selected} + dark={dark} + activeIds={activeIds} + /> + </Show> </div> - <TimeScale charts={charts} scale={scale} /> + <TimeScale firstChart={firstChart} scale={scale} /> </div> ); } diff --git a/app/src/app/components/frames/settings.tsx b/app/src/app/components/frames/settings.tsx index 6d5260055..dc6821ac5 100644 --- a/app/src/app/components/frames/settings.tsx +++ b/app/src/app/components/frames/settings.tsx @@ -34,7 +34,7 @@ export function SettingsFrame({ <div class="space-y-4"> <Title>General - Background - - {children}

; } -function RadioGroup< +export function FieldRadioGroup< T extends | string | { @@ -256,31 +256,54 @@ function RadioGroup<

{description}

-
- - {(value) => ( - - )} - -
+ ); } + +export function RadioGroup< + T extends + | string + | { + text: string; + value: number; + }, +>({ title, sl, size }: { title: string; sl: SL; size?: Size }) { + return ( +
+ + {(value) => ( + + )} + +
+ ); +} diff --git a/app/src/app/types.d.ts b/app/src/app/types.d.ts index 9d3740d06..d6e7ec4bc 100644 --- a/app/src/app/types.d.ts +++ b/app/src/app/types.d.ts @@ -5,3 +5,5 @@ type FrameName = | "Search" | "History" | "Settings"; + +type Size = "xs" | "sm" | "base" | "lg" | "xl"; diff --git a/app/src/scripts/lightweightCharts/create.ts b/app/src/scripts/lightweightCharts/create.ts index 2c1485250..37273eb0d 100644 --- a/app/src/scripts/lightweightCharts/create.ts +++ b/app/src/scripts/lightweightCharts/create.ts @@ -8,17 +8,15 @@ import { colors } from "../utils/colors"; import { valueToString } from "../utils/locale"; import { HorzScaleBehaviorHeight } from "./horzScaleBehavior"; -export function createChart( - scale: ResourceScale, - element: HTMLElement, - { - dark, - priceScaleOptions, - }: { - dark: Accessor; - priceScaleOptions?: DeepPartialPriceScaleOptions; - }, -) { +export function createChart({ + scale, + element, + dark, +}: { + scale: ResourceScale; + element: HTMLElement; + dark: Accessor; +}) { console.log(`chart: create (scale: ${scale})`); const options: DeepPartialChartOptions = { @@ -63,11 +61,9 @@ export function createChart( } chart.priceScale("right").applyOptions({ - ...priceScaleOptions, scaleMargins: { top: 0.075, bottom: 0.075, - ...priceScaleOptions?.scaleMargins, }, minimumWidth: 78, }); diff --git a/app/src/scripts/lightweightCharts/group.ts b/app/src/scripts/lightweightCharts/group.ts new file mode 100644 index 000000000..b1864e835 --- /dev/null +++ b/app/src/scripts/lightweightCharts/group.ts @@ -0,0 +1,209 @@ +import { createRWS } from "/src/solid/rws"; + +import { chunkIdToIndex } from "../datasets/resource"; +import { SeriesType } from "../presets/enums"; +import { stringToId } from "../utils/id"; +import { createBaseLineSeries, DEFAULT_BASELINE_COLORS } from "./baseLine"; +import { createCandlesticksSeries } from "./candlesticks"; +import { createHistogramSeries } from "./histogram"; +import { createSeriesLegend } from "./legend"; +import { createLineSeries } from "./line"; + +export function createSeriesGroup({ + scale, + datasets, + activeIds, + seriesConfig, + preset, + chartLegend, + chart, + index: seriesIndex, + disabled, + lastActiveIndex, + debouncedSetMinMaxMarkers, + dark, +}: { + scale: Scale; + datasets: Datasets; + activeIds: Accessor; + seriesConfig: SeriesConfig; + preset: Preset; + chart: IChartApi; + index: number; + chartLegend: SeriesLegend[]; + lastActiveIndex: Accessor; + disabled?: Accessor; + debouncedSetMinMaxMarkers: VoidFunction; + dark: Accessor; +}) { + const { + datasetPath, + title, + colors, + color, + defaultVisible, + seriesType: type, + options, + priceScaleOptions, + } = seriesConfig; + + const dataset = datasets.getOrImport( + scale, + datasetPath as DatasetPath, + ); + + const seriesList: RWS< + ISeriesApi<"Baseline" | "Line" | "Histogram" | "Candlestick"> | undefined + >[] = new Array(dataset.fetchedJSONs.length); + + const legend = createSeriesLegend({ + scale, + id: stringToId(title), + presetId: preset.id, + title, + seriesList, + color: colors || color || DEFAULT_BASELINE_COLORS, + defaultVisible, + disabled, + dataset, + }); + + chartLegend.push(legend); + + dataset.fetchedJSONs.forEach((json, index) => { + const series: (typeof seriesList)[number] = createRWS(undefined); + + seriesList[index] = series; + + createEffect(() => { + const values = json.vec(); + + if (!values) return; + + if (seriesIndex > 0) { + let previous = chartLegend.at(seriesIndex - 1)?.seriesList[index]; + + if (!previous?.()) { + return; + } + } + + untrack(() => { + let s = series(); + + if (!s) { + switch (type) { + case SeriesType.Based: { + s = createBaseLineSeries({ + chart, + dark, + color, + topColor: seriesConfig.topColor, + bottomColor: seriesConfig.bottomColor, + options, + }); + + break; + } + case SeriesType.Candlestick: { + const candlestickSeries = createCandlesticksSeries({ + chart, + options, + dark, + }); + + s = candlestickSeries[0]; + + if (!colors && !color) { + legend.color = candlestickSeries[1]; + } + + break; + } + case SeriesType.Histogram: { + s = createHistogramSeries({ + chart, + options, + }); + + break; + } + default: + case SeriesType.Line: { + s = createLineSeries({ + chart, + color, + dark, + options, + }); + + break; + } + } + + if (priceScaleOptions) { + s.priceScale().applyOptions(priceScaleOptions); + } + + series.set(s); + } + + s.setData(values); + + debouncedSetMinMaxMarkers(); + }); + }); + + createEffect(() => { + const s = series(); + const currentVec = dataset.fetchedJSONs.at(index)?.vec(); + const nextVec = dataset.fetchedJSONs.at(index + 1)?.vec(); + + if (s && currentVec?.length && nextVec?.length) { + s.update(nextVec[0]); + } + }); + + const isLast = createMemo(() => { + const last = lastActiveIndex(); + return last !== undefined && last === index; + }); + + createEffect(() => { + series()?.applyOptions({ + lastValueVisible: legend.drawn() && isLast(), + }); + }); + + const inRange = createMemo(() => { + const range = activeIds(); + + if (range.length) { + const start = chunkIdToIndex(scale, range.at(0)!); + const end = chunkIdToIndex(scale, range.at(-1)!); + + if (index >= start && index <= end) { + return true; + } + } + + return false; + }); + + const visible = createMemo((previous: boolean) => { + if (legend.disabled()) { + return false; + } + + return previous || inRange(); + }, false); + + createEffect(() => { + series()?.applyOptions({ + visible: legend.drawn() && visible(), + }); + }); + }); + + return legend; +} diff --git a/app/src/scripts/lightweightCharts/price.ts b/app/src/scripts/lightweightCharts/price.ts new file mode 100644 index 000000000..fb993cb71 --- /dev/null +++ b/app/src/scripts/lightweightCharts/price.ts @@ -0,0 +1,48 @@ +import { dateFromTime, getNumberOfDaysBetweenTwoDates } from "../utils/date"; +import { debounce } from "../utils/debounce"; + +export const debouncedUpdateVisiblePriceSeriesType = debounce( + updateVisiblePriceSeriesType, + 50, +); + +export function updateVisiblePriceSeriesType({ + scale, + chart, + logicalRange, + timeRange, + priceSeriesType, +}: { + scale: ResourceScale; + chart: IChartApi; + logicalRange?: LogicalRange; + timeRange?: TimeRange; + priceSeriesType: RWS; +}) { + try { + const width = chart.timeScale().width(); + + let ratio: number; + + if (logicalRange) { + ratio = (logicalRange.to - logicalRange.from) / width; + } else if (timeRange) { + if (scale === "date") { + ratio = getNumberOfDaysBetweenTwoDates( + dateFromTime(timeRange.from), + dateFromTime(timeRange.to), + ); + } else { + ratio = ((timeRange.to as number) - (timeRange.from as number)) / width; + } + } else { + throw Error(); + } + + if (ratio <= 0.5) { + priceSeriesType.set("Candlestick"); + } else { + priceSeriesType.set("Line"); + } + } catch {} +} diff --git a/app/src/scripts/presets/addresses/index.ts b/app/src/scripts/presets/addresses/index.ts index 8037ca591..224cfa001 100644 --- a/app/src/scripts/presets/addresses/index.ts +++ b/app/src/scripts/presets/addresses/index.ts @@ -15,6 +15,7 @@ export function createPresets(scale: ResourceScale): PartialPresetFolder { name: `Total Non Empty Addresses`, title: `Total Non Empty Address`, description: "", + unit: "Count", icon: IconTablerWallet, bottom: [ { @@ -29,6 +30,7 @@ export function createPresets(scale: ResourceScale): PartialPresetFolder { name: `New Addresses`, title: `New Addresses`, description: "", + unit: "Count", icon: IconTablerSparkles, bottom: [ { @@ -43,6 +45,7 @@ export function createPresets(scale: ResourceScale): PartialPresetFolder { name: `Total Addresses Created`, title: `Total Addresses Created`, description: "", + unit: "Count", icon: IconTablerArchive, bottom: [ { @@ -57,6 +60,7 @@ export function createPresets(scale: ResourceScale): PartialPresetFolder { name: `Total Empty Addresses`, title: `Total Empty Addresses`, description: "", + unit: "Count", icon: IconTablerTrash, bottom: [ { @@ -177,8 +181,9 @@ export function createAddressCountPreset({ scale, name: `Address Count`, title: `${name} Address Count`, - icon: IconTablerAddressBook, description: "", + unit: "Count", + icon: IconTablerAddressBook, bottom: [addressCount], }; } diff --git a/app/src/scripts/presets/apply.ts b/app/src/scripts/presets/apply.ts deleted file mode 100644 index 35288472f..000000000 --- a/app/src/scripts/presets/apply.ts +++ /dev/null @@ -1,669 +0,0 @@ -import { requestIdleCallbackPossible } from "/src/env"; -import { createRWS } from "/src/solid/rws"; - -import { chunkIdToIndex } from "../datasets/resource"; -import { - createBaseLineSeries, - DEFAULT_BASELINE_COLORS, -} from "../lightweightCharts/baseLine"; -import { createCandlesticksSeries } from "../lightweightCharts/candlesticks"; -import { createChart } from "../lightweightCharts/create"; -import { createHistogramSeries } from "../lightweightCharts/histogram"; -import { createSeriesLegend } from "../lightweightCharts/legend"; -import { createLineSeries } from "../lightweightCharts/line"; -import { setMinMaxMarkers } from "../lightweightCharts/markers"; -import { - getInitialTimeRange, - initTimeScale, - setActiveIds, - setInitialTimeRange, -} from "../lightweightCharts/time"; -import { setWhitespace } from "../lightweightCharts/whitespace"; -import { colors } from "../utils/colors"; -import { dateFromTime, getNumberOfDaysBetweenTwoDates } from "../utils/date"; -import { debounce } from "../utils/debounce"; -import { stringToId } from "../utils/id"; -import { webSockets } from "../ws"; -import { SeriesType } from "./enums"; - -export function applySeriesList({ - parentDiv, - charts: reactiveChartList, - top, - bottom, - preset, - priceScaleOptions, - datasets, - priceDataset, - priceOptions, - legendSetter, - dark, - activeIds, -}: { - charts: RWS; - parentDiv: HTMLDivElement; - preset: Preset; - legendSetter: Setter; - priceDataset?: AnyDatasetPath; - priceOptions?: PriceSeriesOptions; - // priceScaleOptions?: DeepPartialPriceScaleOptions; - // top?: SeriesConfig[]; - // bottom?: SeriesConfig[]; - datasets: Datasets; - dark: Accessor; - activeIds: RWS; -} & PresetParams) { - // --- - // Reset states - // --- - - legendSetter([]); - - reactiveChartList.set((charts) => { - charts.forEach((chart) => { - chart.remove(); - }); - - return []; - }); - - parentDiv.replaceChildren(); - - // --- - // Done - // --- - - const scale = preset.scale; - - const presetLegend: SeriesLegend[] = []; - - const priceSeriesType = createRWS("Candlestick"); - - const activeDatasets: ResourceDataset[] = []; - - const lastActiveIndex = createMemo(() => { - const last = activeIds().at(-1); - return last !== undefined ? chunkIdToIndex(scale, last) : undefined; - }); - - const exactRange = createRWS(getInitialTimeRange(scale)); - - setActiveIds({ - exactRange: exactRange(), - activeIds: activeIds, - }); - - const seriesNumber = 1 + (top || []).length + (bottom || []).length; - - const charts = [top || [], bottom] - .flatMap((list) => (list ? [list] : [])) - .flatMap((seriesConfigList, chartIndex) => { - if (chartIndex !== 0 && seriesConfigList.length === 0) { - return []; - } - - const div = document.createElement("div"); - - div.className = "w-full cursor-crosshair min-h-0 border-lighter h-full"; - - parentDiv.appendChild(div); - - const chart = createChart(scale, div, { - dark, - priceScaleOptions, - }); - - if (!chart) { - console.log("chart: undefined"); - return []; - } - - const whitespace = setWhitespace(chart, scale); - - const range = exactRange(); - - setInitialTimeRange({ chart, range }); - - if (chartIndex === 0) { - initTimeScale({ - scale, - chart, - activeIds: activeIds, - exactRange, - }); - - if (range) { - updateVisiblePriceSeriesType({ - scale, - chart, - priceSeriesType, - timeRange: range, - }); - } - } - - // const whitespace = new Array | undefined>( - // scale === "date" - // ? whitespaceDateDatasets.length - // : whitespaceHeightDatasets.length, - // ).fill(undefined); - - // function createWhitespaceSeriesIfNeeded(index: number) { - // console.log(index); - // if (index >= 0 && index < whitespace.length && !whitespace[index]) { - // const series = createLineSeries(chart); - // whitespace[index] = series; - - // if (scale === "date") { - // series.setData(whitespaceDateDatasets[index]); - // } else { - // series.setData(whitespaceHeightDatasets[index]); - // } - // } - // } - - // createEffect(() => { - // const ids = activeIds(); - // console.log(ids); - - // const idsLength = ids.length; - // for (let i = 0; i < idsLength; i++) { - // const id = ids[i]; - - // const whitespaceIndex = chunkIdToIndex( - // scale, - // scale === "date" - // ? id - whitespaceStartDateYear - // : id - whitespaceHeightStart, - // ); - - // if (i === 0) { - // createWhitespaceSeriesIfNeeded(whitespaceIndex - 1); - // } - - // createWhitespaceSeriesIfNeeded(whitespaceIndex); - - // if (i === idsLength - 1) { - // createWhitespaceSeriesIfNeeded(whitespaceIndex + 1); - // } - // } - // }); - - const chartLegend: SeriesLegend[] = []; - - onCleanup(() => { - chartLegend.length = 0; - }); - - const markerCallback = () => - setMinMaxMarkers({ - scale, - visibleRange: exactRange(), - legendList: chartLegend, - dark, - activeIds: activeIds, - }); - - const debouncedSetMinMaxMarkers = requestIdleCallbackPossible - ? () => requestIdleCallback(markerCallback) - : debounce( - markerCallback, - seriesNumber * 10 + scale === "date" ? 50 : 100, - ); - - createEffect(on([exactRange, dark], debouncedSetMinMaxMarkers)); - - if (chartIndex === 0) { - const datasetPath = - priceDataset || (`/${scale}-to-price` satisfies AnyDatasetPath); - - const dataset = datasets.getOrImport(scale, datasetPath); - - activeDatasets.push(dataset); - - const title = priceOptions?.title || "Price"; - - const priceScaleOptions: DeepPartialPriceScaleOptions = { - mode: 1, - ...priceOptions?.priceScaleOptions, - }; - - function createPriceSeries(seriesType: PriceSeriesType) { - let seriesConfig: SeriesConfig; - - if (seriesType === "Candlestick") { - seriesConfig = { - // @ts-ignore - datasetPath, - title, - seriesType: SeriesType.Candlestick, - options: priceOptions, - priceScaleOptions, - }; - } else { - seriesConfig = { - // @ts-ignore - datasetPath, - title, - color: colors.white, - options: priceOptions?.seriesOptions, - priceScaleOptions, - }; - } - - const priceSeries = createSeriesGroup({ - scale, - datasets, - index: -1, - activeIds, - seriesConfig, - chart, - chartLegend, - lastActiveIndex, - preset, - disabled: () => priceSeriesType() !== seriesType, - debouncedSetMinMaxMarkers, - dark, - }); - - createEffect(() => { - const latest = webSockets.liveKrakenCandle.latest(); - - if (!latest) return; - - const index = chunkIdToIndex(scale, latest.year); - - const series = priceSeries.seriesList.at(index)?.(); - - series?.update(latest); - }); - - return priceSeries; - } - - const priceCandlestickLegend = createPriceSeries("Candlestick"); - const priceLineLegend = createPriceSeries("Line"); - - createEffect(() => { - priceCandlestickLegend.visible.set(priceLineLegend.visible()); - }); - - createEffect(() => { - priceLineLegend.visible.set(priceCandlestickLegend.visible()); - }); - } - - [...seriesConfigList].reverse().forEach((seriesConfig, index) => { - const dataset = datasets.getOrImport(scale, seriesConfig.datasetPath); - - activeDatasets.push(dataset); - - createSeriesGroup({ - scale, - datasets, - activeIds: activeIds, - index, - seriesConfig, - chartLegend, - chart, - preset, - lastActiveIndex, - debouncedSetMinMaxMarkers, - dark, - }); - }); - - chartLegend.forEach((legend) => { - presetLegend.splice(0, 0, legend); - - createEffect(on(legend.visible, debouncedSetMinMaxMarkers)); - }); - - return [ - { - scale, - div, - chart, - whitespace, - legendList: chartLegend, - debouncedSetMinMaxMarkers, - }, - ]; - }) satisfies ChartObject[]; - - createEffect(() => { - const visibleCharts: typeof charts = []; - - charts.forEach((chart) => { - if (chart.legendList.some((legend) => legend.drawn())) { - chart.div.style.border = ""; - chart.div.style.maxHeight = "100%"; - visibleCharts.push(chart); - } else { - // chart.div.style.height = "100%"; - chart.div.style.maxHeight = "0px"; - chart.div.style.border = "none"; - } - }); - - visibleCharts.forEach(({ div, chart }, index) => { - const last = index === visibleCharts.length - 1; - - div.style.height = last ? "100%" : "calc(100% - 62px)"; - div.style.borderBottomWidth = last ? "none" : "1px"; - div.style.marginBottom = last ? "" : "-2px"; - - chart.timeScale().applyOptions({ - visible: last, - }); - }); - }); - - const debouncedUpdateVisiblePriceSeriesType = debounce( - updateVisiblePriceSeriesType, - 50, - ); - - const activeDatasetsLength = activeDatasets.length; - createEffect(() => { - const range = activeIds(); - - untrack(() => { - for (let i = 0; i < range.length; i++) { - const id = range[i]; - for (let j = 0; j < activeDatasetsLength; j++) { - activeDatasets[j].fetch(id); - } - } - }); - }); - - const lastChartIndex = charts.length - 1; - - for (let i = 0; i < charts.length; i++) { - const chart = charts[i].chart; - - chart.timeScale().subscribeVisibleLogicalRangeChange((logicalRange) => { - if (!logicalRange) return; - - // Must be the chart with the visible timeScale - if (i === lastChartIndex) { - debouncedUpdateVisiblePriceSeriesType({ - scale, - chart, - logicalRange, - priceSeriesType, - }); - } - - for (let j = 0; j <= lastChartIndex; j++) { - if (i !== j) { - charts[j].chart.timeScale().setVisibleLogicalRange(logicalRange); - } - } - }); - - chart.subscribeCrosshairMove(({ time, sourceEvent }) => { - // Don't override crosshair position from scroll event - if (time && !sourceEvent) return; - - for (let j = 0; j <= lastChartIndex; j++) { - const whitespace = charts[j].whitespace; - const otherChart = charts[j].chart; - - if (whitespace && i !== j) { - if (time) { - otherChart.setCrosshairPosition(NaN, time, whitespace); - } else { - // No time when mouse goes outside the chart - otherChart.clearCrosshairPosition(); - } - } - } - }); - } - - legendSetter(presetLegend); - - reactiveChartList.set(() => charts.map(({ chart }) => chart)); -} - -export function updateVisiblePriceSeriesType({ - scale, - chart, - logicalRange, - timeRange, - priceSeriesType, -}: { - scale: ResourceScale; - chart: IChartApi; - logicalRange?: LogicalRange; - timeRange?: TimeRange; - priceSeriesType: RWS; -}) { - try { - const width = chart.timeScale().width(); - - let ratio: number; - - if (logicalRange) { - ratio = (logicalRange.to - logicalRange.from) / width; - } else if (timeRange) { - if (scale === "date") { - ratio = getNumberOfDaysBetweenTwoDates( - dateFromTime(timeRange.from), - dateFromTime(timeRange.to), - ); - } else { - ratio = ((timeRange.to as number) - (timeRange.from as number)) / width; - } - } else { - throw Error(); - } - - if (ratio <= 0.5) { - priceSeriesType.set("Candlestick"); - } else { - priceSeriesType.set("Line"); - } - } catch {} -} - -function createSeriesGroup({ - scale, - datasets, - activeIds, - seriesConfig, - preset, - chartLegend, - chart, - index: seriesIndex, - disabled, - lastActiveIndex, - debouncedSetMinMaxMarkers, - dark, -}: { - scale: Scale; - datasets: Datasets; - activeIds: Accessor; - seriesConfig: SeriesConfig; - preset: Preset; - chart: IChartApi; - index: number; - chartLegend: SeriesLegend[]; - lastActiveIndex: Accessor; - disabled?: Accessor; - debouncedSetMinMaxMarkers: VoidFunction; - dark: Accessor; -}) { - const { - datasetPath, - title, - colors, - color, - defaultVisible, - seriesType: type, - options, - priceScaleOptions, - } = seriesConfig; - - const dataset = datasets.getOrImport( - scale, - datasetPath as DatasetPath, - ); - - const seriesList: RWS< - ISeriesApi<"Baseline" | "Line" | "Histogram" | "Candlestick"> | undefined - >[] = new Array(dataset.fetchedJSONs.length); - - const legend = createSeriesLegend({ - scale, - id: stringToId(title), - presetId: preset.id, - title, - seriesList, - color: colors || color || DEFAULT_BASELINE_COLORS, - defaultVisible, - disabled, - dataset, - }); - - chartLegend.push(legend); - - dataset.fetchedJSONs.forEach((json, index) => { - const series: (typeof seriesList)[number] = createRWS(undefined); - - seriesList[index] = series; - - createEffect(() => { - const values = json.vec(); - - if (!values) return; - - if (seriesIndex > 0) { - let previous = chartLegend.at(seriesIndex - 1)?.seriesList[index]; - - if (!previous?.()) { - return; - } - } - - untrack(() => { - let s = series(); - - if (!s) { - switch (type) { - case SeriesType.Based: { - s = createBaseLineSeries({ - chart, - dark, - color, - topColor: seriesConfig.topColor, - bottomColor: seriesConfig.bottomColor, - options, - }); - - break; - } - case SeriesType.Candlestick: { - const candlestickSeries = createCandlesticksSeries({ - chart, - options, - dark, - }); - - s = candlestickSeries[0]; - - if (!colors && !color) { - legend.color = candlestickSeries[1]; - } - - break; - } - case SeriesType.Histogram: { - s = createHistogramSeries({ - chart, - options, - }); - - break; - } - default: - case SeriesType.Line: { - s = createLineSeries({ - chart, - color, - dark, - options, - }); - - break; - } - } - - if (priceScaleOptions) { - s.priceScale().applyOptions(priceScaleOptions); - } - - series.set(s); - } - - s.setData(values); - - debouncedSetMinMaxMarkers(); - }); - }); - - createEffect(() => { - const s = series(); - const currentVec = dataset.fetchedJSONs.at(index)?.vec(); - const nextVec = dataset.fetchedJSONs.at(index + 1)?.vec(); - - if (s && currentVec?.length && nextVec?.length) { - s.update(nextVec[0]); - } - }); - - const isLast = createMemo(() => { - const last = lastActiveIndex(); - return last !== undefined && last === index; - }); - - createEffect(() => { - series()?.applyOptions({ - lastValueVisible: legend.drawn() && isLast(), - }); - }); - - const inRange = createMemo(() => { - const range = activeIds(); - - if (range.length) { - const start = chunkIdToIndex(scale, range.at(0)!); - const end = chunkIdToIndex(scale, range.at(-1)!); - - if (index >= start && index <= end) { - return true; - } - } - - return false; - }); - - const visible = createMemo((previous: boolean) => { - if (legend.disabled()) { - return false; - } - - return previous || inRange(); - }, false); - - createEffect(() => { - series()?.applyOptions({ - visible: legend.drawn() && visible(), - }); - }); - }); - - return legend; -} diff --git a/app/src/scripts/presets/blocks/index.ts b/app/src/scripts/presets/blocks/index.ts index 349b8ce42..944512d76 100644 --- a/app/src/scripts/presets/blocks/index.ts +++ b/app/src/scripts/presets/blocks/index.ts @@ -13,6 +13,7 @@ export function createPresets(scale: ResourceScale) { name: "Height", title: "Block Height", description: "", + unit: "Height", bottom: [ { title: "Height", @@ -31,6 +32,7 @@ export function createPresets(scale: ResourceScale) { name: "Daily Sum", title: "Daily Sum Of Blocks Mined", description: "", + unit: "Count", bottom: [ { title: "Target", @@ -64,6 +66,7 @@ export function createPresets(scale: ResourceScale) { name: "Weekly Sum", title: "Weekly Sum Of Blocks Mined", description: "", + unit: "Count", bottom: [ { title: "Target", @@ -86,6 +89,7 @@ export function createPresets(scale: ResourceScale) { name: "Monthly Sum", title: "Monthly Sum Of Blocks Mined", description: "", + unit: "Count", bottom: [ { title: "Target", @@ -108,6 +112,7 @@ export function createPresets(scale: ResourceScale) { name: "Yearly Sum", title: "Yearly Sum Of Blocks Mined", description: "", + unit: "Count", bottom: [ { title: "Target", @@ -130,6 +135,7 @@ export function createPresets(scale: ResourceScale) { name: "Total", title: "Total Blocks Mined", description: "", + unit: "Count", bottom: [ { title: "Mined", @@ -147,6 +153,7 @@ export function createPresets(scale: ResourceScale) { scale, title: "Block Size", color: colors.darkWhite, + unit: "Megabytes", keySum: "/date-to-block-size-1d-sum", keyAverage: "/date-to-block-size-1d-average", keyMax: "/date-to-block-size-1d-max", @@ -165,6 +172,7 @@ export function createPresets(scale: ResourceScale) { scale, title: "Block Weight", color: colors.darkWhite, + unit: "Weight", keyAverage: "/date-to-block-weight-1d-average", keyMax: "/date-to-block-weight-1d-max", key90p: "/date-to-block-weight-1d-90p", @@ -182,6 +190,7 @@ export function createPresets(scale: ResourceScale) { scale, title: "Block VBytes", color: colors.darkWhite, + unit: "Virtual Bytes", keyAverage: "/date-to-block-vbytes-1d-average", keyMax: "/date-to-block-vbytes-1d-max", key90p: "/date-to-block-vbytes-1d-90p", @@ -199,6 +208,7 @@ export function createPresets(scale: ResourceScale) { scale, title: "Block Interval", color: colors.darkWhite, + unit: "Seconds", keyAverage: "/date-to-block-interval-1d-average", keyMax: "/date-to-block-interval-1d-max", key90p: "/date-to-block-interval-1d-90p", @@ -217,9 +227,10 @@ export function createPresets(scale: ResourceScale) { name: "Size", title: "Block Size", description: "", + unit: "Megabytes", bottom: [ { - title: "Size (MB)", + title: "Size", color: colors.darkWhite, datasetPath: `/height-to-block-size`, }, @@ -231,9 +242,10 @@ export function createPresets(scale: ResourceScale) { name: "Weight", title: "Block Weight", description: "", + unit: "Weight", bottom: [ { - title: "Weight (MB)", + title: "Weight", color: colors.darkWhite, datasetPath: `/height-to-block-weight`, }, @@ -245,6 +257,7 @@ export function createPresets(scale: ResourceScale) { name: "VBytes", title: "Block VBytes", description: "", + unit: "Virtual Bytes", bottom: [ { title: "VBytes", @@ -259,9 +272,10 @@ export function createPresets(scale: ResourceScale) { name: "Interval", title: "Block Interval", description: "", + unit: "Seconds", bottom: [ { - title: "Interval (s)", + title: "Interval", color: colors.darkWhite, datasetPath: `/height-to-block-interval`, }, @@ -274,9 +288,10 @@ export function createPresets(scale: ResourceScale) { name: "Cumulative Size", title: "Cumulative Block Size", description: "", + unit: "Megabytes", bottom: [ { - title: "Size (MB)", + title: "Size", color: colors.darkWhite, datasetPath: `/${scale}-to-cumulative-block-size`, }, diff --git a/app/src/scripts/presets/cointime/index.ts b/app/src/scripts/presets/cointime/index.ts index 838f203a3..6e3245103 100644 --- a/app/src/scripts/presets/cointime/index.ts +++ b/app/src/scripts/presets/cointime/index.ts @@ -15,6 +15,7 @@ export function createPresets(scale: ResourceScale) { name: "All", title: "All Cointime Prices", description: "", + unit: "US Dollars", top: [ { title: "Vaulted Price", @@ -52,6 +53,7 @@ export function createPresets(scale: ResourceScale) { name: "Price", title: "Active Price", description: "", + unit: "US Dollars", top: [ { title: "Active Price", @@ -78,6 +80,7 @@ export function createPresets(scale: ResourceScale) { name: "Price", title: "Vaulted Price", description: "", + unit: "US Dollars", top: [ { title: "Vaulted Price", @@ -104,6 +107,7 @@ export function createPresets(scale: ResourceScale) { name: "Price", title: "True Market Mean", description: "", + unit: "US Dollars", top: [ { title: "True Market Mean", @@ -130,6 +134,7 @@ export function createPresets(scale: ResourceScale) { name: "Price", title: "Cointime Price", description: "", + unit: "US Dollars", top: [ { title: "Cointime", @@ -158,9 +163,7 @@ export function createPresets(scale: ResourceScale) { name: "All", title: "Cointime Capitalizations", description: "", - priceScaleOptions: { - mode: 1, - }, + unit: "US Dollars", bottom: [ { title: "Market Cap", @@ -190,9 +193,7 @@ export function createPresets(scale: ResourceScale) { name: "Thermo Cap", title: "Thermo Cap", description: "", - priceScaleOptions: { - mode: 1, - }, + unit: "US Dollars", bottom: [ { title: "Thermo Cap", @@ -207,10 +208,7 @@ export function createPresets(scale: ResourceScale) { name: "Investor Cap", title: "Investor Cap", description: "", - - priceScaleOptions: { - mode: 1, - }, + unit: "US Dollars", bottom: [ { title: "Investor Cap", @@ -223,8 +221,9 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerDivide, name: "Thermo Cap To Investor Cap Ratio", - title: "Thermo Cap To Investor Cap Ratio (%)", + title: "Thermo Cap To Investor Cap Ratio", description: "", + unit: "Percentage", bottom: [ { title: "Ratio", @@ -244,6 +243,7 @@ export function createPresets(scale: ResourceScale) { name: "All", title: "All Coinblocks", description: "", + unit: "Coinblocks", bottom: [ { title: "Coinblocks Created", @@ -268,6 +268,7 @@ export function createPresets(scale: ResourceScale) { name: "Created", title: "Coinblocks Created", description: "", + unit: "Coinblocks", bottom: [ { title: "Coinblocks Created", @@ -282,7 +283,7 @@ export function createPresets(scale: ResourceScale) { name: "Destroyed", title: "Coinblocks Destroyed", description: "", - + unit: "Coinblocks", bottom: [ { title: "Coinblocks Destroyed", @@ -297,6 +298,7 @@ export function createPresets(scale: ResourceScale) { name: "Stored", title: "Coinblocks Stored", description: "", + unit: "Coinblocks", bottom: [ { title: "Coinblocks Stored", @@ -316,6 +318,7 @@ export function createPresets(scale: ResourceScale) { name: "All", title: "All Cumulative Coinblocks", description: "", + unit: "Coinblocks", bottom: [ { title: "Cumulative Coinblocks Created", @@ -340,6 +343,7 @@ export function createPresets(scale: ResourceScale) { name: "Created", title: "Cumulative Coinblocks Created", description: "", + unit: "Coinblocks", bottom: [ { title: "Cumulative Coinblocks Created", @@ -354,6 +358,7 @@ export function createPresets(scale: ResourceScale) { name: "Destroyed", title: "Cumulative Coinblocks Destroyed", description: "", + unit: "Coinblocks", bottom: [ { title: "Cumulative Coinblocks Destroyed", @@ -368,6 +373,7 @@ export function createPresets(scale: ResourceScale) { name: "Stored", title: "Cumulative Coinblocks Stored", description: "", + unit: "Coinblocks", bottom: [ { title: "Cumulative Coinblocks Stored", @@ -387,6 +393,7 @@ export function createPresets(scale: ResourceScale) { name: "Liveliness - Activity", title: "Liveliness (Activity)", description: "", + unit: "", bottom: [ { title: "Liveliness", @@ -401,6 +408,7 @@ export function createPresets(scale: ResourceScale) { name: "Vaultedness", title: "Vaultedness", description: "", + unit: "", bottom: [ { title: "Vaultedness", @@ -415,6 +423,7 @@ export function createPresets(scale: ResourceScale) { name: "Versus", title: "Liveliness V. Vaultedness", description: "", + unit: "", bottom: [ { title: "Liveliness", @@ -434,6 +443,7 @@ export function createPresets(scale: ResourceScale) { name: "Activity To Vaultedness Ratio", title: "Activity To Vaultedness Ratio", description: "", + unit: "Percentage", bottom: [ { title: "Activity To Vaultedness Ratio", @@ -448,15 +458,16 @@ export function createPresets(scale: ResourceScale) { name: "Concurrent Liveliness - Supply Adjusted Coindays Destroyed", title: "Concurrent Liveliness - Supply Adjusted Coindays Destroyed", description: "", + unit: "", bottom: [ { title: "Concurrent Liveliness 14d Median", - color: colors.darkLiveliness, + color: colors.liveliness, datasetPath: `/${scale}-to-concurrent-liveliness-2w-median`, }, { title: "Concurrent Liveliness", - color: colors.liveliness, + color: colors.darkLiveliness, datasetPath: `/${scale}-to-concurrent-liveliness`, }, ], @@ -467,6 +478,7 @@ export function createPresets(scale: ResourceScale) { name: "Liveliness Incremental Change", title: "Liveliness Incremental Change", description: "", + unit: "", bottom: [ { title: "Liveliness Incremental Change", @@ -493,6 +505,7 @@ export function createPresets(scale: ResourceScale) { name: "Vaulted", title: "Vaulted Supply", description: "", + unit: "Bitcoin", bottom: [ { title: "Vaulted Supply", @@ -507,7 +520,7 @@ export function createPresets(scale: ResourceScale) { name: "Active", title: "Active Supply", description: "", - + unit: "Bitcoin", bottom: [ { title: "Active Supply", @@ -522,7 +535,7 @@ export function createPresets(scale: ResourceScale) { name: "Vaulted V. Active", title: "Vaulted V. Active", description: "", - + unit: "Bitcoin", bottom: [ { title: "Circulating Supply", @@ -577,6 +590,7 @@ export function createPresets(scale: ResourceScale) { name: "Vaulted Net Change", title: "Vaulted Supply Net Change", description: "", + unit: "Bitcoin", bottom: [ { title: "Vaulted Supply Net Change", @@ -591,6 +605,7 @@ export function createPresets(scale: ResourceScale) { name: "Active Net Change", title: "Active Supply Net Change", description: "", + unit: "Bitcoin", bottom: [ { title: "Active Supply Net Change", @@ -605,6 +620,7 @@ export function createPresets(scale: ResourceScale) { name: "Active VS. Vaulted 90D Net Change", title: "Active VS. Vaulted 90 Day Supply Net Change", description: "", + unit: "Bitcoin", bottom: [ { title: "Active Supply Net Change", @@ -724,6 +740,7 @@ export function createPresets(scale: ResourceScale) { name: "In Profit", title: "Cointime Supply In Profit", description: "", + unit: "Bitcoin", bottom: [ { title: "Circulating Supply", @@ -748,6 +765,7 @@ export function createPresets(scale: ResourceScale) { name: "In Loss", title: "Cointime Supply In Loss", description: "", + unit: "Bitcoin", bottom: [ { title: "Circulating Supply", @@ -772,11 +790,9 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerBuildingFactory, name: "Cointime Yearly Inflation Rate", - title: "Cointime-Adjusted Yearly Inflation Rate (%)", + title: "Cointime-Adjusted Yearly Inflation Rate", description: "", - priceScaleOptions: { - mode: 1, - }, + unit: "Percentage", bottom: [ { title: "Cointime Adjusted", @@ -796,9 +812,7 @@ export function createPresets(scale: ResourceScale) { name: "Cointime Velocity", title: "Cointime-Adjusted Transactions Velocity", description: "", - priceScaleOptions: { - mode: 1, - }, + unit: "", bottom: [ { title: "Cointime Adjusted", diff --git a/app/src/scripts/presets/hodlers/index.ts b/app/src/scripts/presets/hodlers/index.ts index 1997f858b..d620dc55a 100644 --- a/app/src/scripts/presets/hodlers/index.ts +++ b/app/src/scripts/presets/hodlers/index.ts @@ -18,6 +18,7 @@ export function createPresets(scale: ResourceScale) { title: `Hodl Supply`, description: "", icon: IconTablerRipple, + unit: "Bitcoin", bottom: [ { title: `24h`, diff --git a/app/src/scripts/presets/market/averages/index.ts b/app/src/scripts/presets/market/averages/index.ts index bb409e617..987a7e97d 100644 --- a/app/src/scripts/presets/market/averages/index.ts +++ b/app/src/scripts/presets/market/averages/index.ts @@ -1,7 +1,6 @@ import { averages } from "/src/scripts/datasets/date"; import { colors } from "/src/scripts/utils/colors"; -import { SeriesType } from "../../enums"; import { createRatioFolder } from "../../templates/ratio"; export function createPresets(scale: ResourceScale): PartialPresetFolder { @@ -14,6 +13,7 @@ export function createPresets(scale: ResourceScale): PartialPresetFolder { name: "All", title: "All Moving Averages", description: "", + unit: "US Dollars", top: averages.map((average) => ({ title: average.key.toUpperCase(), color: colors[`_${average.key}`], @@ -52,9 +52,10 @@ function createPresetFolder({ { scale, name: "Average", - description: "", - icon: IconTablerMathAvg, title, + description: "", + unit: "US Dollars", + icon: IconTablerMathAvg, top: [ { title: `SMA`, diff --git a/app/src/scripts/presets/market/index.ts b/app/src/scripts/presets/market/index.ts index 1211c274c..d25bb1e71 100644 --- a/app/src/scripts/presets/market/index.ts +++ b/app/src/scripts/presets/market/index.ts @@ -13,6 +13,7 @@ export function createPresets(scale: ResourceScale) { name: "Price", title: "Market Price", description: "", + unit: "US Dollars", }, { scale, @@ -20,6 +21,7 @@ export function createPresets(scale: ResourceScale) { name: "Capitalization", title: "Market Capitalization", description: "", + unit: "US Dollars", bottom: [ { title: "Market Cap.", diff --git a/app/src/scripts/presets/market/returns/index.ts b/app/src/scripts/presets/market/returns/index.ts index 5d2a7b2ac..b67597e40 100644 --- a/app/src/scripts/presets/market/returns/index.ts +++ b/app/src/scripts/presets/market/returns/index.ts @@ -56,9 +56,10 @@ function createPreset({ description: "", icon: IconTablerReceiptTax, title: `${title} Return`, + unit: "Percentage", bottom: [ { - title: `Return (%)`, + title: `Return`, seriesType: SeriesType.Based, datasetPath: `/date-to-price-${key}-return`, }, diff --git a/app/src/scripts/presets/miners/index.ts b/app/src/scripts/presets/miners/index.ts index 67fd346f8..1b9b92974 100644 --- a/app/src/scripts/presets/miners/index.ts +++ b/app/src/scripts/presets/miners/index.ts @@ -17,8 +17,9 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerCoinBitcoin, name: "In Bitcoin", - title: "Last Coinbase (In Bitcoin)", + title: "Last Coinbase In Bitcoin", description: "", + unit: "US Dollars", bottom: [ { title: "Last", @@ -31,8 +32,9 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerCoin, name: "In Dollars", - title: "Last Coinbase (In Dollars)", + title: "Last Coinbase In Dollars", description: "", + unit: "US Dollars", bottom: [ { title: "Last", @@ -51,11 +53,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerMoneybag, name: "In Bitcoin", - title: "Daily Sum Of Bitcoin Coinbases", + title: "Daily Sum Of Coinbases In Bitcoin", description: "", + unit: "Bitcoin", bottom: [ { - title: "Coinbases (Bitcoin)", + title: "Sum", color: colors.bitcoin, datasetPath: `/${scale}-to-coinbase`, }, @@ -65,11 +68,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerCash, name: "In Dollars", - title: "Daily Sum Of Dollar Coinbases", + title: "Daily Sum Of Coinbases In Dollars", description: "", + unit: "US Dollars", bottom: [ { - title: "Coinbases (Dollars)", + title: "Sum", color: colors.dollars, datasetPath: `/${scale}-to-coinbase-in-dollars`, }, @@ -85,11 +89,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerMoneybag, name: "In Bitcoin", - title: "Yearly Sum Of Bitcoin Coinbases", + title: "Yearly Sum Of Coinbases In Bitcoin", description: "", + unit: "Bitcoin", bottom: [ { - title: "Coinbases (Bitcoin)", + title: "Sum", color: colors.bitcoin, datasetPath: `/${scale}-to-coinbase-1y-sum`, }, @@ -99,11 +104,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerCash, name: "In Dollars", - title: "Yearly Sum Of Dollar Coinbases", + title: "Yearly Sum Of Coinbases In Dollars", description: "", + unit: "US Dollars", bottom: [ { - title: "Coinbases (Dollars)", + title: "Sum", color: colors.dollars, datasetPath: `/${scale}-to-coinbase-in-dollars-1y-sum`, }, @@ -119,11 +125,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerMoneybag, name: "In Bitcoin", - title: "Cumulative Bitcoin Coinbases", + title: "Cumulative Coinbases In Bitcoin", description: "", + unit: "Bitcoin", bottom: [ { - title: "Coinbases (Bitcoin)", + title: "Coinbases", color: colors.bitcoin, datasetPath: `/${scale}-to-cumulative-coinbase`, }, @@ -133,11 +140,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerCash, name: "In Dollars", - title: "Cumulative Dollar Coinbases", + title: "Cumulative Coinbases In Dollars", description: "", + unit: "US Dollars", bottom: [ { - title: "Coinbases (Dollars)", + title: "Coinbases", color: colors.dollars, datasetPath: `/${scale}-to-cumulative-coinbase-in-dollars`, }, @@ -162,8 +170,9 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerCoinBitcoin, name: "In Bitcoin", - title: "Last Subsidy (In Bitcoin)", + title: "Last Subsidy In Bitcoin", description: "", + unit: "Bitcoin", bottom: [ { title: "Last", @@ -176,8 +185,9 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerCoin, name: "In Dollars", - title: "Last Subsidy (In Dollars)", + title: "Last Subsidy In Dollars", description: "", + unit: "US Dollars", bottom: [ { title: "Last", @@ -196,11 +206,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerMoneybag, name: "In Bitcoin", - title: "Daily Sum Of Bitcoin Subsidies", + title: "Daily Sum Of Subsidies In Bitcoin", description: "", + unit: "Bitcoin", bottom: [ { - title: "Subsidies (Bitcoin)", + title: "Sum", color: colors.bitcoin, datasetPath: `/${scale}-to-subsidy`, }, @@ -210,11 +221,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerCash, name: "In Dollars", - title: "Daily Sum Of Dollar Subsidies", + title: "Daily Sum Of Subsidies In Dollars", description: "", + unit: "US Dollars", bottom: [ { - title: "Subsidies (Dollars)", + title: "Sum", color: colors.dollars, datasetPath: `/${scale}-to-subsidy-in-dollars`, }, @@ -230,11 +242,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerMoneybag, name: "In Bitcoin", - title: "Yearly Sum Of Bitcoin Subsidies", + title: "Yearly Sum Of Subsidies In Bitcoin", description: "", + unit: "Bitcoin", bottom: [ { - title: "Subsidies (Bitcoin)", + title: "Sum", color: colors.bitcoin, datasetPath: `/${scale}-to-subsidy-1y-sum`, }, @@ -244,11 +257,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerCash, name: "In Dollars", - title: "Yearly Sum Of Dollar Subsidies", + title: "Yearly Sum Of Subsidies In Dollars", description: "", + unit: "US Dollars", bottom: [ { - title: "Subsidies (Dollars)", + title: "Sum", color: colors.dollars, datasetPath: `/${scale}-to-subsidy-in-dollars-1y-sum`, }, @@ -264,11 +278,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerMoneybag, name: "In Bitcoin", - title: "Cumulative Bitcoin Subsidies", + title: "Cumulative Subsidies In Bitcoin", description: "", + unit: "Bitcoin", bottom: [ { - title: "Subsidies (Bitcoin)", + title: "Subsidies", color: colors.bitcoin, datasetPath: `/${scale}-to-cumulative-subsidy`, }, @@ -278,11 +293,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerCash, name: "In Dollars", - title: "Cumulative Dollar Subsidies", + title: "Cumulative Subsidies In Dollars", description: "", + unit: "US Dollars", bottom: [ { - title: "Subsidies (Dollars)", + title: "Subsidies", color: colors.dollars, datasetPath: `/${scale}-to-cumulative-subsidy-in-dollars`, }, @@ -307,8 +323,9 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerCoinBitcoin, name: "In Bitcoin", - title: "Last Fees (In Bitcoin)", + title: "Last Fees In Bitcoin", description: "", + unit: "Bitcoin", bottom: [ { title: "Last", @@ -321,8 +338,9 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerCoin, name: "In Dollars", - title: "Last Fees (In Dollars)", + title: "Last Fees In Dollars", description: "", + unit: "US Dollars", bottom: [ { title: "Last", @@ -341,11 +359,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerMoneybag, name: "In Bitcoin", - title: "Daily Sum Of Bitcoin Fees", + title: "Daily Sum Of Fees In Bitcoin", description: "", + unit: "Bitcoin", bottom: [ { - title: "Fees (Bitcoin)", + title: "Sum", color: colors.bitcoin, datasetPath: `/${scale}-to-fees`, }, @@ -355,11 +374,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerCash, name: "In Dollars", - title: "Daily Sum Of Dollar Fees", + title: "Daily Sum Of Fees In Dollars", description: "", + unit: "US Dollars", bottom: [ { - title: "Fees (Dollars)", + title: "Sum", color: colors.dollars, datasetPath: `/${scale}-to-fees-in-dollars`, }, @@ -375,11 +395,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerMoneybag, name: "In Bitcoin", - title: "Yearly Sum Of Bitcoin Fees", + title: "Yearly Sum Of Fees In Bitcoin", description: "", + unit: "Bitcoin", bottom: [ { - title: "Fees (Bitcoin)", + title: "Sum", color: colors.bitcoin, datasetPath: `/${scale}-to-fees-1y-sum`, }, @@ -389,11 +410,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerCash, name: "In Dollars", - title: "Yearly Sum Of Dollar Fees", + title: "Yearly Sum Of Fees In Dollars", description: "", + unit: "US Dollars", bottom: [ { - title: "Fees (Dollars)", + title: "Sum", color: colors.dollars, datasetPath: `/${scale}-to-fees-in-dollars-1y-sum`, }, @@ -409,11 +431,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerMoneybag, name: "In Bitcoin", - title: "Cumulative Bitcoin Fees", + title: "Cumulative Fees In Bitcoin", description: "", + unit: "Bitcoin", bottom: [ { - title: "Fees (Bitcoin)", + title: "Fees", color: colors.bitcoin, datasetPath: `/${scale}-to-cumulative-fees`, }, @@ -423,11 +446,12 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerCash, name: "In Dollars", - title: "Cumulative Dollar Fees", + title: "Cumulative Fees In Dollars", description: "", + unit: "US Dollars", bottom: [ { - title: "Fees (Dollars)", + title: "Fees", color: colors.dollars, datasetPath: `/${scale}-to-cumulative-fees-in-dollars`, }, @@ -446,14 +470,15 @@ export function createPresets(scale: ResourceScale) { name: "Subsidy V. Fees", title: "Subsidy V. Fees", description: "", + unit: "Percentage", bottom: [ { - title: "Subsidy (%)", + title: "Subsidy", color: colors.bitcoin, datasetPath: `/${scale}-to-subsidy-to-coinbase-ratio`, }, { - title: "Fees (%)", + title: "Fees", color: colors.darkBitcoin, datasetPath: `/${scale}-to-fees-to-coinbase-ratio`, }, @@ -468,9 +493,7 @@ export function createPresets(scale: ResourceScale) { name: "Puell Multiple", title: "Puell Multiple", description: "", - priceScaleOptions: { - mode: 1, - }, + unit: "", bottom: [ { title: "Multiple", @@ -484,11 +507,9 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerPick, name: "Hash Rate", - title: "Hash Rate (EH/s)", + title: "Hash Rate", description: "", - priceScaleOptions: { - mode: 1, - }, + unit: "ExaHash / Second", bottom: [ { title: "1M SMA", @@ -511,11 +532,9 @@ export function createPresets(scale: ResourceScale) { scale, icon: IconTablerRibbonHealth, name: "Hash Ribbon", - title: "Hash Ribbon (EH/s)", + title: "Hash Ribbon", description: "", - priceScaleOptions: { - mode: 1, - }, + unit: "ExaHash / Second", bottom: [ { title: "1M SMA", @@ -535,15 +554,16 @@ export function createPresets(scale: ResourceScale) { name: "Hash Price", title: "Hash Price", description: "", + unit: "Dollars / (PetaHash / Second)", bottom: [ { - title: "Price ($/PH/s)", + title: "Price", color: colors.dollars, datasetPath: `/date-to-hash-price`, }, ], }, - ] satisfies PartialPreset[]) + ] as const satisfies PartialPreset[]) : []), { @@ -552,9 +572,7 @@ export function createPresets(scale: ResourceScale) { name: "Difficulty", title: "Difficulty", description: "", - priceScaleOptions: { - mode: 1, - }, + unit: "", bottom: [ { title: "Difficulty", @@ -572,9 +590,10 @@ export function createPresets(scale: ResourceScale) { name: "Difficulty Adjustment", title: "Difficulty Adjustment", description: "", + unit: "Percentage", bottom: [ { - title: "Adjustment (%)", + title: "Adjustment", seriesType: SeriesType.Based, datasetPath: `/${scale}-to-difficulty-adjustment`, }, @@ -589,9 +608,7 @@ export function createPresets(scale: ResourceScale) { name: "Annualized Issuance", title: "Annualized Issuance", description: "", - priceScaleOptions: { - mode: 1, - }, + unit: "Bitcoin", bottom: [ { title: "Issuance", @@ -607,12 +624,10 @@ export function createPresets(scale: ResourceScale) { name: "Yearly Inflation Rate", title: "Yearly Inflation Rate", description: "", - priceScaleOptions: { - mode: 1, - }, + unit: "Percentage", bottom: [ { - title: "Rate (%)", + title: "Rate", color: colors.bitcoin, datasetPath: `/${scale}-to-yearly-inflation-rate`, }, diff --git a/app/src/scripts/presets/templates/cohort/pricesPaid.ts b/app/src/scripts/presets/templates/cohort/pricesPaid.ts index 617f33a7e..239784ee7 100644 --- a/app/src/scripts/presets/templates/cohort/pricesPaid.ts +++ b/app/src/scripts/presets/templates/cohort/pricesPaid.ts @@ -21,6 +21,7 @@ export function createCohortPresetPricesPaidFolder({ name: `Average`, title: `${title} Average Price Paid - Realized Price`, description: "", + unit: "US Dollars", icon: () => IconTablerMathAvg, top: [ { @@ -36,6 +37,7 @@ export function createCohortPresetPricesPaidFolder({ title: `${title} deciles`, icon: () => IconTablerSquareHalf, description: "", + unit: "US Dollars", top: percentiles .filter(({ value }) => Number(value) % 10 === 0) .map(({ name, id }) => { @@ -54,6 +56,7 @@ export function createCohortPresetPricesPaidFolder({ name: percentile.name, title: `${title} ${percentile.title}`, description: "", + unit: "US Dollars", icon: () => IconTablerSquareHalf, top: [ { diff --git a/app/src/scripts/presets/templates/cohort/realized.ts b/app/src/scripts/presets/templates/cohort/realized.ts index 5f22093ab..77f5555f8 100644 --- a/app/src/scripts/presets/templates/cohort/realized.ts +++ b/app/src/scripts/presets/templates/cohort/realized.ts @@ -25,6 +25,7 @@ export function createCohortPresetRealizedFolder({ name: `Price`, title: `${title} Realized Price`, description: "", + unit: "US Dollars", icon: () => IconTablerTag, top: [ { @@ -46,6 +47,7 @@ export function createCohortPresetRealizedFolder({ name: `Capitalization`, title: `${title} Realized Capitalization`, description: "", + unit: "US Dollars", icon: () => IconTablerPigMoney, bottom: [ { @@ -70,6 +72,7 @@ export function createCohortPresetRealizedFolder({ name: `Capitalization 1M Net Change`, title: `${title} Realized Capitalization 1 Month Net Change`, description: "", + unit: "US Dollars", icon: () => IconTablerStatusChange, bottom: [ { @@ -84,6 +87,7 @@ export function createCohortPresetRealizedFolder({ name: `Profit`, title: `${title} Realized Profit`, description: "", + unit: "US Dollars", icon: () => IconTablerCash, bottom: [ { @@ -98,6 +102,7 @@ export function createCohortPresetRealizedFolder({ name: "Loss", title: `${title} Realized Loss`, description: "", + unit: "US Dollars", icon: () => IconTablerCoffin, bottom: [ { @@ -112,6 +117,7 @@ export function createCohortPresetRealizedFolder({ name: `PNL`, title: `${title} Realized Profit And Loss`, description: "", + unit: "US Dollars", icon: () => IconTablerArrowsVertical, bottom: [ { @@ -133,6 +139,7 @@ export function createCohortPresetRealizedFolder({ name: `Net PNL`, title: `${title} Net Realized Profit And Loss`, description: "", + unit: "US Dollars", icon: () => IconTablerScale, bottom: [ { @@ -147,6 +154,7 @@ export function createCohortPresetRealizedFolder({ name: `Net PNL Relative To Market Cap`, title: `${title} Net Realized Profit And Loss Relative To Market Capitalization`, description: "", + unit: "Percentage", icon: () => IconTablerDivide, bottom: [ { @@ -161,6 +169,7 @@ export function createCohortPresetRealizedFolder({ name: `Cumulative Profit`, title: `${title} Cumulative Realized Profit`, description: "", + unit: "US Dollars", icon: () => IconTablerSum, bottom: [ { @@ -175,6 +184,7 @@ export function createCohortPresetRealizedFolder({ name: "Cumulative Loss", title: `${title} Cumulative Realized Loss`, description: "", + unit: "US Dollars", icon: () => IconTablerSum, bottom: [ { @@ -189,6 +199,7 @@ export function createCohortPresetRealizedFolder({ name: `Cumulative Net PNL`, title: `${title} Cumulative Net Realized Profit And Loss`, description: "", + unit: "US Dollars", icon: () => IconTablerSum, bottom: [ { @@ -203,6 +214,7 @@ export function createCohortPresetRealizedFolder({ name: `Cumulative Net PNL 30 Day Change`, title: `${title} Cumulative Net Realized Profit And Loss 30 Day Change`, description: "", + unit: "US Dollars", icon: () => IconTablerTimeDuration30, bottom: [ { @@ -217,6 +229,7 @@ export function createCohortPresetRealizedFolder({ name: `Value Created`, title: `${title} Value Created`, description: "", + unit: "US Dollars", icon: () => IconTablerPlus, bottom: [ { @@ -231,6 +244,7 @@ export function createCohortPresetRealizedFolder({ name: `Value Destroyed`, title: `${title} Value Destroyed`, description: "", + unit: "US Dollars", icon: () => IconTablerMinus, bottom: [ { @@ -245,6 +259,7 @@ export function createCohortPresetRealizedFolder({ name: `Spent Output Profit Ratio - SOPR`, title: `${title} Spent Output Profit Ratio`, description: "", + unit: "Percentage", icon: () => IconTablerMathXDivideY, bottom: [ { diff --git a/app/src/scripts/presets/templates/cohort/supply.ts b/app/src/scripts/presets/templates/cohort/supply.ts index 3543d76a3..157d553c8 100644 --- a/app/src/scripts/presets/templates/cohort/supply.ts +++ b/app/src/scripts/presets/templates/cohort/supply.ts @@ -27,7 +27,7 @@ export function createCohortPresetSupplyFolder({ title: `${title} Profit And Loss`, icon: () => IconTablerArrowsCross, description: "", - + unit: "US Dollars", bottom: [ { title: "In Profit", @@ -60,6 +60,7 @@ export function createCohortPresetSupplyFolder({ title: `${title} Total supply`, icon: () => IconTablerSum, description: "", + unit: "Bitcoin", bottom: [ { title: "Supply", @@ -73,6 +74,7 @@ export function createCohortPresetSupplyFolder({ name: "In Profit", title: `${title} Supply In Profit`, description: "", + unit: "Bitcoin", icon: () => IconTablerTrendingUp, bottom: [ { @@ -87,6 +89,7 @@ export function createCohortPresetSupplyFolder({ name: "In Loss", title: `${title} Supply In Loss`, description: "", + unit: "Bitcoin", icon: () => IconTablerTrendingDown, bottom: [ { @@ -106,6 +109,7 @@ export function createCohortPresetSupplyFolder({ name: "All", title: `${title} Profit And Loss Relative To Circulating Supply`, description: "", + unit: "Percentage", icon: () => IconTablerArrowsCross, bottom: [ { @@ -138,6 +142,7 @@ export function createCohortPresetSupplyFolder({ name: `Total`, title: `${title} Total supply Relative To Circulating Supply`, description: "", + unit: "Percentage", icon: () => IconTablerSum, bottom: [ { @@ -152,6 +157,7 @@ export function createCohortPresetSupplyFolder({ name: "In Profit", title: `${title} Supply In Profit Relative To Circulating Supply`, description: "", + unit: "Percentage", icon: () => IconTablerTrendingUp, bottom: [ { @@ -166,6 +172,7 @@ export function createCohortPresetSupplyFolder({ name: "In Loss", title: `${title} Supply In Loss Relative To Circulating Supply`, description: "", + unit: "Percentage", icon: () => IconTablerTrendingDown, bottom: [ { @@ -185,6 +192,7 @@ export function createCohortPresetSupplyFolder({ name: "All", title: `${title} Supply In Profit And Loss Relative To Own Supply`, description: "", + unit: "Percentage", icon: () => IconTablerArrowsCross, bottom: [ { @@ -221,6 +229,7 @@ export function createCohortPresetSupplyFolder({ name: "In Profit", title: `${title} Supply In Profit Relative To Own Supply`, description: "", + unit: "Percentage", icon: () => IconTablerTrendingUp, bottom: [ { @@ -235,6 +244,7 @@ export function createCohortPresetSupplyFolder({ name: "In Loss", title: `${title} Supply In Loss Relative To Own Supply`, description: "", + unit: "Percentage", icon: () => IconTablerTrendingDown, bottom: [ { diff --git a/app/src/scripts/presets/templates/cohort/unrealized.ts b/app/src/scripts/presets/templates/cohort/unrealized.ts index f23748d3f..21cda5e3b 100644 --- a/app/src/scripts/presets/templates/cohort/unrealized.ts +++ b/app/src/scripts/presets/templates/cohort/unrealized.ts @@ -24,6 +24,7 @@ export function createCohortPresetUnrealizedFolder({ name: `Profit`, title: `${title} Unrealized Profit`, description: "", + unit: "US Dollars", icon: () => IconTablerMoodDollar, bottom: [ { @@ -38,6 +39,7 @@ export function createCohortPresetUnrealizedFolder({ name: "Loss", title: `${title} Unrealized Loss`, description: "", + unit: "US Dollars", icon: () => IconTablerMoodSadDizzy, bottom: [ { @@ -52,6 +54,7 @@ export function createCohortPresetUnrealizedFolder({ name: `PNL`, title: `${title} Unrealized Profit And Loss`, description: "", + unit: "US Dollars", icon: () => IconTablerArrowsVertical, bottom: [ { @@ -73,6 +76,7 @@ export function createCohortPresetUnrealizedFolder({ name: `Net PNL`, title: `${title} Net Unrealized Profit And Loss`, description: "", + unit: "US Dollars", icon: () => IconTablerScale, bottom: [ { @@ -87,6 +91,7 @@ export function createCohortPresetUnrealizedFolder({ name: `Net PNL Relative To Market Cap`, title: `${title} Net Unrealized Profit And Loss Relative To Total Market Capitalization`, description: "", + unit: "Percentage", icon: () => IconTablerDivide, bottom: [ { diff --git a/app/src/scripts/presets/templates/cohort/utxo.ts b/app/src/scripts/presets/templates/cohort/utxo.ts index 9135560dc..7ba85d6e0 100644 --- a/app/src/scripts/presets/templates/cohort/utxo.ts +++ b/app/src/scripts/presets/templates/cohort/utxo.ts @@ -21,6 +21,7 @@ export function createCohortPresetUTXOFolder({ name: `Count`, title: `${title} Unspent Transaction Outputs Count`, description: "", + unit: "Count", icon: () => IconTablerTicket, bottom: [ { diff --git a/app/src/scripts/presets/templates/ratio.ts b/app/src/scripts/presets/templates/ratio.ts index af7d67972..126fc975b 100644 --- a/app/src/scripts/presets/templates/ratio.ts +++ b/app/src/scripts/presets/templates/ratio.ts @@ -20,9 +20,10 @@ export function createRatioFolder({ { scale, name: "Basic", - description: "", icon: IconTablerMathXDivideY, title: `Market Price To ${title} Ratio`, + unit: "Ratio", + description: "", top: [ { title: `SMA`, @@ -55,6 +56,7 @@ export function createRatioFolder({ name: "Averages", description: "", icon: IconTablerMathAvg, + unit: "Ratio", title: `Market Price To ${title} Ratio Averages`, top: [ { @@ -98,9 +100,10 @@ export function createRatioFolder({ { scale, name: "Momentum Oscillator", - description: "", - icon: IconTablerWaveSine, title: `Market Price To ${title} Ratio 1Y SMA Momentum Oscillator`, + description: "", + unit: "Ratio", + icon: IconTablerWaveSine, top: [ { title: `SMA`, @@ -129,9 +132,10 @@ export function createRatioFolder({ { scale, name: "Top Percentiles", - description: "", icon: IconTablerJetpack, title: `Market Price To ${title} Ratio Top Percentiles`, + description: "", + unit: "Ratio", top: [ { title: `SMA`, @@ -165,9 +169,10 @@ export function createRatioFolder({ { scale, name: "Bottom Percentiles", - description: "", icon: IconTablerScubaMask, title: `Market Price To ${title} Ratio Bottom Percentiles`, + description: "", + unit: "Ratio", top: [ { title: `SMA`, @@ -201,9 +206,10 @@ export function createRatioFolder({ { scale, name: "Top Probabilities", - description: "", icon: IconTablerRocket, title: `${title} Top Probabilities`, + description: "", + unit: "US Dollars", top: [ { title: `0.1%`, @@ -225,9 +231,10 @@ export function createRatioFolder({ { scale, name: "Bottom Probabilities", - description: "", icon: IconTablerSubmarine, title: `${title} Bottom Probabilities`, + description: "", + unit: "US Dollars", top: [ { title: `0.1%`, diff --git a/app/src/scripts/presets/templates/recap.ts b/app/src/scripts/presets/templates/recap.ts index f0a2c409a..377843971 100644 --- a/app/src/scripts/presets/templates/recap.ts +++ b/app/src/scripts/presets/templates/recap.ts @@ -1,5 +1,6 @@ export function createRecapPresets({ scale, + unit, title, keyAverage, color, @@ -15,6 +16,7 @@ export function createRecapPresets({ scale: ResourceScale; title: string; color: Color; + unit: Unit; keySum?: AnyDatasetPath; keyAverage?: AnyDatasetPath; keyMax?: AnyDatasetPath; @@ -34,6 +36,7 @@ export function createRecapPresets({ name: "Daily Sum", title: `${title} Daily Sum`, description: "", + unit, bottom: [ { title: "Sum", @@ -52,6 +55,7 @@ export function createRecapPresets({ name: "Daily Average", title: `${title} Daily Average`, description: "", + unit, bottom: [ { title: "Average", @@ -70,6 +74,7 @@ export function createRecapPresets({ name: "Daily Percentiles", title: `${title} Daily Percentiles`, description: "", + unit, bottom: [ ...(keyMax ? [ @@ -146,6 +151,7 @@ export function createRecapPresets({ name: "Daily Max", title: `${title} Daily Max`, description: "", + unit, bottom: [ { title: "Max", @@ -164,6 +170,7 @@ export function createRecapPresets({ name: "Daily 90th Percentile", title: `${title} Daily 90th Percentile`, description: "", + unit, bottom: [ { title: "90%", @@ -182,6 +189,7 @@ export function createRecapPresets({ name: "Daily 75th Percentile", title: `${title} Size 75th Percentile`, description: "", + unit, bottom: [ { title: "75%", @@ -200,6 +208,7 @@ export function createRecapPresets({ name: "Daily Median", title: `${title} Daily Median`, description: "", + unit, bottom: [ { title: "Median", @@ -218,6 +227,7 @@ export function createRecapPresets({ name: "Daily 25th Percentile", title: `${title} Daily 25th Percentile`, description: "", + unit, bottom: [ { title: "25%", @@ -236,6 +246,7 @@ export function createRecapPresets({ name: "Daily 10th Percentile", title: `${title} Daily 10th Percentile`, description: "", + unit, bottom: [ { title: "10%", @@ -254,6 +265,7 @@ export function createRecapPresets({ name: "Daily Min", title: `${title} Daily Min`, description: "", + unit, bottom: [ { title: "Min", diff --git a/app/src/scripts/presets/transactions/index.ts b/app/src/scripts/presets/transactions/index.ts index f71bd2d41..8393ec399 100644 --- a/app/src/scripts/presets/transactions/index.ts +++ b/app/src/scripts/presets/transactions/index.ts @@ -10,6 +10,7 @@ export function createPresets(scale: ResourceScale) { name: "Count", title: "Transaction Count", description: "", + unit: "Count", bottom: [ { title: "1M SMA", @@ -38,6 +39,7 @@ export function createPresets(scale: ResourceScale) { name: "In Bitcoin", title: "Transaction Volume", description: "", + unit: "Bitcoin", bottom: [ { title: "1M SMA", @@ -62,9 +64,7 @@ export function createPresets(scale: ResourceScale) { name: "In Dollars", title: "Transaction Volume In Dollars", description: "", - priceScaleOptions: { - mode: 1, - }, + unit: "US Dollars", bottom: [ { title: "1M SMA", @@ -95,6 +95,7 @@ export function createPresets(scale: ResourceScale) { name: "In Bitcoin", title: "Annualized Transaction Volume", description: "", + unit: "Bitcoin", bottom: [ { title: "Volume", @@ -109,6 +110,7 @@ export function createPresets(scale: ResourceScale) { name: "In Dollars", title: "Annualized Transaction Volume In Dollars", description: "", + unit: "US Dollars", bottom: [ { title: "Volume", @@ -125,6 +127,7 @@ export function createPresets(scale: ResourceScale) { name: "Velocity", title: "Transactions Velocity", description: "", + unit: "", bottom: [ { title: "Transactions Velocity", @@ -139,6 +142,7 @@ export function createPresets(scale: ResourceScale) { name: "Per Second", title: "Transactions Per Second", description: "", + unit: "Transactions", bottom: [ { title: "1M SMA", diff --git a/app/src/scripts/presets/types.d.ts b/app/src/scripts/presets/types.d.ts index 72e7656ee..3cf7aab78 100644 --- a/app/src/scripts/presets/types.d.ts +++ b/app/src/scripts/presets/types.d.ts @@ -1,13 +1,30 @@ interface PresetParams { - priceScaleOptions?: DeepPartialPriceScaleOptions; top?: SeriesConfig[]; bottom?: SeriesConfig[]; } +type Unit = + | "US Dollars" + | "Bitcoin" + | "Percentage" + | "Height" + | "Count" + | "Megabytes" + | "Transactions" + | "Weight" + | "Ratio" + | "Virtual Bytes" + | "Seconds" + | "Coinblocks" + | "ExaHash / Second" + | "Dollars / (PetaHash / Second)" + | ""; + type PartialPreset = { scale: ResourceScale; icon?: () => JSXElement; name: string; + unit: Unit; title: string; description: string; } & PresetParams; @@ -24,16 +41,6 @@ type FilePath = { name: string; }[]; -// type ApplyPreset = (params: { -// charts: RWS; -// parentDiv: HTMLDivElement; -// datasets: Datasets; -// preset: Preset; -// legendSetter: Setter; -// dark: Accessor; -// activeIds: RWS; -// }) => void; - interface PartialPresetFolder { name: string; tree: PartialPresetTree; diff --git a/app/src/scripts/utils/selectableList/static/index.ts b/app/src/scripts/utils/selectableList/static/index.ts index 7ee5d16c0..d786e2b98 100644 --- a/app/src/scripts/utils/selectableList/static/index.ts +++ b/app/src/scripts/utils/selectableList/static/index.ts @@ -49,10 +49,10 @@ export const createStaticList = ( return found; } else { + const index = savedIndex ?? parameters.selectedIndex; + const value = index !== undefined ? l.at(index) : undefined; return ( - l.at(savedIndex ?? parameters.selectedIndex!) ?? - parameters.defaultValue ?? - l[parameters.defaultIndex || 0] + value ?? parameters.defaultValue ?? l[parameters.defaultIndex || 0] ); } }), diff --git a/app/src/solid/idle.ts b/app/src/solid/idle.ts new file mode 100644 index 000000000..cc923e11d --- /dev/null +++ b/app/src/solid/idle.ts @@ -0,0 +1,26 @@ +import { requestIdleCallbackPossible } from "../env"; +import { createRWS } from "./rws"; + +export function createWasIdleAccessor() { + const wasIdle = createRWS(false); + + if (requestIdleCallbackPossible) { + const idleCallback = requestIdleCallback(() => { + wasIdle.set(true); + }); + + onCleanup(() => { + cancelIdleCallback(idleCallback); + }); + } else { + const timeout = setTimeout(() => { + wasIdle.set(true); + }, 500); + + onCleanup(() => { + clearTimeout(timeout); + }); + } + + return wasIdle as Accessor; +} diff --git a/app/src/types/lightweight-charts.d.ts b/app/src/types/lightweight-charts.d.ts index 3fd528865..6dad9566e 100644 --- a/app/src/types/lightweight-charts.d.ts +++ b/app/src/types/lightweight-charts.d.ts @@ -4,6 +4,7 @@ type ISeriesApi = import("lightweight-charts").ISeriesApi; type SeriesOptionsMap = import("lightweight-charts").SeriesOptionsMap; type ISeriesApiAny = ISeriesApi; +type ITimeScaleApi