general: snapshot

This commit is contained in:
k
2024-07-03 20:40:35 +02:00
parent b7e8cbea20
commit 069311dcf3
24 changed files with 407 additions and 312 deletions
+3 -2
View File
@@ -9,10 +9,11 @@
- General
- Added a light theme !
- Performance
- Added height datasets and many optimizations to make them usable
- Added split panes in order to always have the vertical axis visible
- Improved app's reactivity
- Added some chunk splitting for a faster initial load
- Global improvements that increased the Lighthouse's performance score from the low 30s to the high 70s
- Chart
- Global improvements that increased the Lighthouse's performance score
- Fixed legend hovering on mobile not resetting on touch end
- Updated legend padding so that the scrollbar, if visible, is less in the way
- Added "3 months" and yearly time scale setters (from year 2009 to today)
+3 -1
View File
@@ -4,7 +4,7 @@
## Description
Satonomics is a Bitcoin only on-chain data generator and visualizer that is fully free, open source, verifiable, local-first and self-hostable.
Satonomics is a better, FOSS, Bitcoin-only, self-hostable Glassnode.
While [mempool.space](https://mempool.space) gives a very micro view of the network where you can follow the journey of any address, this tool on the other hand, is the exact opposite and very complimentary by giving you a much more global/macro view of the flow and various dynamics of the network.
@@ -19,9 +19,11 @@ This project is in a very early stage. The web app will have bugs, the API might
## Instances
Web App:
- [app.satonomics.xyz](https://app.satonomics.xyz)
API:
- [api.satonomics.xyz](https://api.satonomics.xyz)
## Structure
+1 -2
View File
@@ -19,7 +19,6 @@
"@leeoniya/ufuzzy": "^1.0.14",
"@solid-primitives/event-listener": "^2.3.3",
"@solid-primitives/intersection-observer": "^2.1.6",
"@solid-primitives/memo": "^1.3.8",
"@solid-primitives/resize-observer": "^2.0.25",
"lean-qr": "^2.3.4",
"lightweight-charts": "^4.1.6",
@@ -36,7 +35,7 @@
"pwa-asset-generator": "^6.3.1",
"rollup-plugin-visualizer": "^5.12.0",
"tailwindcss": "^3.4.4",
"typescript": "^5.5.2",
"typescript": "^5.5.3",
"unplugin-auto-import": "^0.17.6",
"unplugin-icons": "^0.19.0",
"vite": "^5.3.2",
+21 -42
View File
@@ -14,9 +14,6 @@ dependencies:
'@solid-primitives/intersection-observer':
specifier: ^2.1.6
version: 2.1.6(solid-js@1.8.18)
'@solid-primitives/memo':
specifier: ^1.3.8
version: 1.3.8(solid-js@1.8.18)
'@solid-primitives/resize-observer':
specifier: ^2.0.25
version: 2.0.25(solid-js@1.8.18)
@@ -62,8 +59,8 @@ devDependencies:
specifier: ^3.4.4
version: 3.4.4
typescript:
specifier: ^5.5.2
version: 5.5.2
specifier: ^5.5.3
version: 5.5.3
unplugin-auto-import:
specifier: ^0.17.6
version: 0.17.6(rollup@2.79.1)
@@ -2251,16 +2248,6 @@ packages:
solid-js: 1.8.18
dev: false
/@solid-primitives/memo@1.3.8(solid-js@1.8.18):
resolution: {integrity: sha512-U75pfLFSxFmM2xbx1+2XPPyWbaXrnUFF10spbFuOUgJ7azrC+4y+FnrVi4RKqHw9gftd8aKQuTiyMQq468YLQw==}
peerDependencies:
solid-js: ^1.6.12
dependencies:
'@solid-primitives/scheduled': 1.4.3(solid-js@1.8.18)
'@solid-primitives/utils': 6.2.3(solid-js@1.8.18)
solid-js: 1.8.18
dev: false
/@solid-primitives/resize-observer@2.0.25(solid-js@1.8.18):
resolution: {integrity: sha512-jVDXkt2MiriYRaz4DYs62185d+6jQ+1DCsR+v7f6XMsIJJuf963qdBRFjtZtKXBaxdPNMyuPeDgf5XQe3EoDJg==}
peerDependencies:
@@ -2282,14 +2269,6 @@ packages:
solid-js: 1.8.18
dev: false
/@solid-primitives/scheduled@1.4.3(solid-js@1.8.18):
resolution: {integrity: sha512-HfWN5w7b7FEc6VPLBKnnE302h90jsLMuR28Fcf7neRGGf8jBj6wm6/UFQ00VlKexHFMR6KQ2u4VBh5a1ZcqM8g==}
peerDependencies:
solid-js: ^1.6.12
dependencies:
solid-js: 1.8.18
dev: false
/@solid-primitives/static-store@0.0.8(solid-js@1.8.18):
resolution: {integrity: sha512-ZecE4BqY0oBk0YG00nzaAWO5Mjcny8Fc06CdbXadH9T9lzq/9GefqcSe/5AtdXqjvY/DtJ5C6CkcjPZO0o/eqg==}
peerDependencies:
@@ -2528,7 +2507,7 @@ packages:
postcss: ^8.1.0
dependencies:
browserslist: 4.23.1
caniuse-lite: 1.0.30001638
caniuse-lite: 1.0.30001639
fraction.js: 4.3.7
normalize-range: 0.1.2
picocolors: 1.0.1
@@ -2655,8 +2634,8 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
dependencies:
caniuse-lite: 1.0.30001638
electron-to-chromium: 1.4.815
caniuse-lite: 1.0.30001639
electron-to-chromium: 1.4.816
node-releases: 2.0.14
update-browserslist-db: 1.0.16(browserslist@4.23.1)
dev: true
@@ -2715,8 +2694,8 @@ packages:
engines: {node: '>=6'}
dev: true
/caniuse-lite@1.0.30001638:
resolution: {integrity: sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==}
/caniuse-lite@1.0.30001639:
resolution: {integrity: sha512-eFHflNTBIlFwP2AIKaYuBQN/apnUoKNhBdza8ZnW/h2di4LCZ4xFqYlxUxo+LQ76KFI1PGcC1QDxMbxTZpSCAg==}
dev: true
/capnp-ts@0.7.0:
@@ -3122,8 +3101,8 @@ packages:
jake: 10.9.1
dev: true
/electron-to-chromium@1.4.815:
resolution: {integrity: sha512-OvpTT2ItpOXJL7IGcYakRjHCt8L5GrrN/wHCQsRB4PQa1X9fe+X9oen245mIId7s14xvArCGSTIq644yPUKKLg==}
/electron-to-chromium@1.4.816:
resolution: {integrity: sha512-EKH5X5oqC6hLmiS7/vYtZHZFTNdhsYG5NVPRN6Yn0kQHNBlT59+xSM8HBy66P5fxWpKgZbPqb+diC64ng295Jw==}
dev: true
/emoji-regex@8.0.0:
@@ -4125,7 +4104,7 @@ packages:
engines: {node: '>=14'}
dependencies:
mlly: 1.7.1
pkg-types: 1.1.1
pkg-types: 1.1.2
dev: true
/locate-path@5.0.0:
@@ -4349,7 +4328,7 @@ packages:
dependencies:
acorn: 8.12.0
pathe: 1.1.2
pkg-types: 1.1.1
pkg-types: 1.1.2
ufo: 1.5.3
dev: true
@@ -4632,8 +4611,8 @@ packages:
find-up: 4.1.0
dev: true
/pkg-types@1.1.1:
resolution: {integrity: sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==}
/pkg-types@1.1.2:
resolution: {integrity: sha512-VEGf1he2DR5yowYRl0XJhWJq5ktm9gYIsH+y8sNJpHlxch7JPDaufgrsl4vYjd9hMUY8QVjoNncKbow9I7exyA==}
dependencies:
confbox: 0.1.7
mlly: 1.7.1
@@ -5671,8 +5650,8 @@ packages:
possible-typed-array-names: 1.0.0
dev: true
/typescript@5.5.2:
resolution: {integrity: sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==}
/typescript@5.5.3:
resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==}
engines: {node: '>=14.17'}
hasBin: true
dev: true
@@ -5754,10 +5733,10 @@ packages:
magic-string: 0.30.10
mlly: 1.7.1
pathe: 1.1.2
pkg-types: 1.1.1
pkg-types: 1.1.2
scule: 1.3.0
strip-literal: 2.1.0
unplugin: 1.10.2
unplugin: 1.11.0
transitivePeerDependencies:
- rollup
dev: true
@@ -5793,7 +5772,7 @@ packages:
magic-string: 0.30.10
minimatch: 9.0.5
unimport: 3.7.2(rollup@2.79.1)
unplugin: 1.10.2
unplugin: 1.11.0
transitivePeerDependencies:
- rollup
dev: true
@@ -5824,13 +5803,13 @@ packages:
debug: 4.3.5
kolorist: 1.8.0
local-pkg: 0.5.0
unplugin: 1.10.2
unplugin: 1.11.0
transitivePeerDependencies:
- supports-color
dev: true
/unplugin@1.10.2:
resolution: {integrity: sha512-KuPqnjU4HBcrSwmQatfdc5hU4xzaQrhoKqCKylwmLnbBvqj5udXL8cHrkOuYDoI4ESCwJIiAIKMujroIUKLgow==}
/unplugin@1.11.0:
resolution: {integrity: sha512-3r7VWZ/webh0SGgJScpWl2/MRCZK5d3ZYFcNaeci/GQ7Teop7zf0Nl2pUuz7G21BwPd9pcUPOC5KmJ2L3WgC5g==}
engines: {node: '>=14.0.0'}
dependencies:
acorn: 8.12.0
@@ -4,14 +4,12 @@ export function Chart({
presets,
datasets,
legendSetter,
activeResources,
}: {
charts: RWS<IChartApi[]>;
parentDiv: RWS<HTMLDivElement | undefined>;
presets: Presets;
datasets: Datasets;
legendSetter: Setter<PresetLegend>;
activeResources: Accessor<Set<ResourceDataset<any, any>>>;
}) {
onMount(() => {
createEffect(() => {
@@ -28,7 +26,6 @@ export function Chart({
parentDiv: div,
datasets,
preset,
activeResources,
legendSetter,
});
} catch (error) {
@@ -11,7 +11,6 @@ import { Title } from "./components/title";
export function ChartFrame({
presets,
datasets,
activeResources,
hide,
qrcode,
standalone,
@@ -20,7 +19,6 @@ export function ChartFrame({
presets: Presets;
hide?: Accessor<boolean>;
qrcode: RWS<string>;
activeResources: Accessor<Set<ResourceDataset<any, any>>>;
datasets: Datasets;
fullscreen?: RWS<boolean>;
standalone: boolean;
@@ -64,7 +62,6 @@ export function ChartFrame({
<Chart
parentDiv={div}
charts={charts}
activeResources={activeResources}
datasets={datasets}
legendSetter={legend.set}
presets={presets}
+1 -9
View File
@@ -136,13 +136,7 @@ export function App() {
},
);
const activeResources = createRWS<Set<ResourceDataset<any, any>>>(new Set(), {
equals: false,
});
const datasets = createDatasets({
setActiveResources: activeResources.set,
});
const datasets = createDatasets();
onMount(() => {
webSockets.openAll();
@@ -273,7 +267,6 @@ export function App() {
qrcode={qrcode}
standalone={false}
datasets={datasets}
activeResources={activeResources}
/>
</Show>
@@ -340,7 +333,6 @@ export function App() {
presets={presets}
qrcode={qrcode}
fullscreen={fullscreen}
activeResources={activeResources}
datasets={datasets}
/>
</div>
-4
View File
@@ -4,11 +4,9 @@ export { averages } from "./consts/averages";
export function createScaleDatasets<Scale extends ResourceScale>({
scale,
setActiveResources,
groupedKeysToURLPath,
}: {
scale: Scale;
setActiveResources: Setter<Set<ResourceDataset<any, any>>>;
groupedKeysToURLPath: GroupedKeysToURLPath[Scale];
}) {
type Key = keyof typeof groupedKeysToURLPath;
@@ -23,7 +21,6 @@ export function createScaleDatasets<Scale extends ResourceScale>({
datasets[key as unknown as Exclude<Key, "ohlc">] = createResourceDataset({
scale,
path: groupedKeysToURLPath[key as Key] as any,
setActiveResources,
});
}
}
@@ -31,7 +28,6 @@ export function createScaleDatasets<Scale extends ResourceScale>({
const price = createResourceDataset<Scale, OHLC>({
scale,
path: `/${scale}-to-ohlc`,
setActiveResources,
});
Object.assign(datasets, { price });
-4
View File
@@ -3,10 +3,8 @@ import { createResourceDataset } from "./resource";
export { averages } from "./consts/averages";
export function createDateDatasets({
setActiveResources,
groupedKeysToURLPath,
}: {
setActiveResources: Setter<Set<ResourceDataset<any, any>>>;
groupedKeysToURLPath: GroupedKeysToURLPath["date"];
}) {
type Key = keyof typeof groupedKeysToURLPath;
@@ -21,7 +19,6 @@ export function createDateDatasets({
datasets[key as Exclude<Key, "ohlc">] = createResourceDataset<"date">({
scale: "date",
path: groupedKeysToURLPath[key as Key],
setActiveResources,
});
}
}
@@ -29,7 +26,6 @@ export function createDateDatasets({
const price = createResourceDataset<"date", OHLC>({
scale: "date",
path: "/date-to-ohlc",
setActiveResources,
});
Object.assign(datasets, { price });
-4
View File
@@ -1,10 +1,8 @@
import { createResourceDataset } from "./resource";
export function createHeightDatasets({
setActiveResources,
groupedKeysToURLPath,
}: {
setActiveResources: Setter<Set<ResourceDataset<any, any>>>;
groupedKeysToURLPath: GroupedKeysToURLPath["height"];
}) {
type Key = keyof typeof groupedKeysToURLPath;
@@ -19,7 +17,6 @@ export function createHeightDatasets({
datasets[key as Exclude<Key, "ohlc">] = createResourceDataset<"height">({
scale: "height",
path: groupedKeysToURLPath[key as Key],
setActiveResources,
});
}
}
@@ -27,7 +24,6 @@ export function createHeightDatasets({
const price = createResourceDataset<"height", OHLC>({
scale: "height",
path: "/height-to-ohlc",
setActiveResources,
});
Object.assign(datasets, { price });
+1 -7
View File
@@ -7,18 +7,12 @@ export const scales = ["date" as const, "height" as const];
export const HEIGHT_CHUNK_SIZE = 10_000;
export function createDatasets({
setActiveResources,
}: {
setActiveResources: Setter<Set<ResourceDataset<any, any>>>;
}) {
export function createDatasets() {
const date = createDateDatasets({
setActiveResources,
groupedKeysToURLPath: groupedKeysToURLPath.date,
});
const height = createHeightDatasets({
setActiveResources,
groupedKeysToURLPath: groupedKeysToURLPath.height,
});
+39 -27
View File
@@ -1,5 +1,3 @@
import { createLazyMemo } from "@solid-primitives/memo";
import {
ONE_DAY_IN_MS,
ONE_HOUR_IN_MS,
@@ -8,19 +6,12 @@ import {
import { createRWS } from "/src/solid/rws";
import { HEIGHT_CHUNK_SIZE } from ".";
import { debounce } from "../utils/debounce";
export function createResourceDataset<
Scale extends ResourceScale,
Type extends OHLC | number = number,
>({
scale,
path,
setActiveResources,
}: {
scale: Scale;
path: string;
setActiveResources: Setter<Set<ResourceDataset<any, any>>>;
}) {
>({ scale, path }: { scale: Scale; path: string }) {
type Dataset = Scale extends "date"
? FetchedDateDataset<Type>
: FetchedHeightDataset<Type>;
@@ -85,8 +76,7 @@ export function createResourceDataset<
}) as FetchedResult<Scale, Type>[];
const _fetch = async (id: number) => {
const index =
scale === "date" ? id - 2009 : Math.floor(id / HEIGHT_CHUNK_SIZE);
const index = chunkIdToIndex(scale, id);
if (
index < 0 ||
@@ -205,25 +195,43 @@ export function createResourceDataset<
fetched.loading = false;
};
const valuesCallback = (vecs: Value[][]) => {
let length = 0;
for (let i = 0; i < vecs.length; i++) {
length += vecs[i].length;
}
if (!length) return;
const array = new Array(length);
let k = 0;
for (let i = 0; i < vecs.length; i++) {
let vec = vecs[i];
for (let j = 0; j < vec.length; j++) {
array[k++] = vec[j];
}
}
if (k !== length) throw Error("e");
values.set(array);
};
const debouncedValuesCallback = debounce(valuesCallback, 100);
const values = createRWS<Value[]>([]);
createEffect(() => {
const vecs = fetchedJSONs.map((fetched) => fetched.vec() || []);
debouncedValuesCallback(vecs);
});
const resource: ResourceDataset<Scale, Type> = {
scale,
url: baseURL,
fetch: _fetch,
fetchedJSONs,
values: createLazyMemo(() => {
setActiveResources((resources) => resources.add(resource));
onCleanup(() =>
setActiveResources((resources) => {
resources.delete(resource);
return resources;
}),
);
const flat = fetchedJSONs.flatMap((fetched) => fetched.vec() || []);
return flat;
}),
values,
drop() {
fetchedJSONs.forEach((fetched) => {
fetched.at = null;
@@ -245,3 +253,7 @@ async function convertResponseToJSON<
return null;
}
}
function chunkIdToIndex(scale: ResourceScale, id: number) {
return scale === "date" ? id - 2009 : Math.floor(id / HEIGHT_CHUNK_SIZE);
}
+3 -12
View File
@@ -8,14 +8,6 @@ type ResourceScale = (typeof import("./index").scales)[index];
type DatasetValue<T> = T & Numbered & Valued;
interface Dataset<
Scale extends ResourceScale,
Value extends SingleValueData | CandlestickData = SingleValueData,
> {
scale: Scale;
values: Accessor<DatasetValue<Value>[]>;
}
interface ResourceDataset<
Scale extends ResourceScale,
Type extends OHLC | number = number,
@@ -27,16 +19,15 @@ interface ResourceDataset<
Value extends SingleValueData | CandlestickData = Type extends number
? SingleValueData
: CandlestickData,
> extends Dataset<Scale, Value> {
> {
scale: Scale;
url: string;
fetch: (id: number) => void;
fetchedJSONs: FetchedResult<Scale, Type>[];
values: Accessor<DatasetValue<Value>[]>;
drop: VoidFunction;
}
type AnyDataset<Scale extends ResourceScale> = Dataset<Scale> &
Partial<ResourceDataset<Scale>>;
interface FetchedResult<
Scale extends ResourceScale,
Type extends number | OHLC,
@@ -1,110 +0,0 @@
import { colors } from "../../utils/colors";
import { webSockets } from "../../ws";
import { createCandlesticksSeries } from "../series/creators/candlesticks";
import { createSeriesLegend } from "../series/creators/legend";
import { createLineSeries } from "../series/creators/line";
import { initTimeScale } from "./time";
export const PRICE_SCALE_MOMENTUM_ID = "momentum";
export const applyPriceSeries = <
Scale extends ResourceScale,
T extends SingleValueData,
>({
chart,
preset,
dataset,
seriesType,
valuesSkipped,
options,
activeResources,
}: {
chart: IChartApi;
preset: Preset;
valuesSkipped: Accessor<number>;
seriesType: Accessor<"Candlestick" | "Line">;
activeResources: Accessor<Set<ResourceDataset<any, any>>>;
dataset: Dataset<Scale, T>;
options?: PriceSeriesOptions;
}) => {
const id = options?.id || "price";
const title = options?.title || "Price";
const url = "url" in dataset ? (dataset as any).url : undefined;
const priceScaleOptions: DeepPartialPriceScaleOptions = {
mode: 1,
...options?.priceScaleOptions,
};
const [ohlcSeries, ohlcColors] = createCandlesticksSeries(chart, options);
const ohlcLegend = createSeriesLegend({
id,
presetId: preset.id,
title,
color: () => ohlcColors,
series: ohlcSeries,
disabled: () => seriesType() !== "Candlestick",
url,
});
ohlcSeries.priceScale().applyOptions(priceScaleOptions);
// ---
const lineColor = colors.white;
const lineSeries = createLineSeries(chart, {
color: lineColor,
...options?.seriesOptions,
});
const lineLegend = createSeriesLegend({
id,
presetId: preset.id,
title,
color: () => lineColor,
series: lineSeries,
disabled: () => seriesType() !== "Line",
visible: ohlcLegend.visible,
url,
});
lineSeries.priceScale().applyOptions(priceScaleOptions);
// ---
// setMinMaxMarkers({
// scale: preset.scale,
// candlesticks:
// dataset?.values() || datasets[preset.scale].price.values() || ([] as any),
// range: chartState.range,
// lowerOpacity,
// });
initTimeScale({
activeResources,
});
createEffect(() => {
const values = dataset.values();
// const values = computeDrawnSeriesValues(dataset.values(), valuesSkipped());
lineSeries.setData(values);
ohlcSeries.setData(values);
});
createEffect(() => {
if (preset.scale === "date") {
const latest = webSockets.liveKrakenCandle.latest();
if (latest) {
ohlcSeries.update(latest);
lineSeries.update(latest);
}
}
});
return { ohlcLegend, lineLegend };
};
@@ -1,7 +1,6 @@
import { HEIGHT_CHUNK_SIZE } from "../../datasets";
import { debounce } from "../../utils/debounce";
import { writeURLParam } from "../../utils/urlParams";
import { setMinMaxMarkers } from "./markers";
import {
chartState,
LOCAL_STORAGE_RANGE_KEY,
@@ -20,9 +19,9 @@ const debouncedUpdateURLParams = debounce((range: TimeRange | null) => {
}, 500);
export function initTimeScale({
activeResources,
activeDatasets,
}: {
activeResources: Accessor<Set<ResourceDataset<any, any>>>;
activeDatasets: Set<ResourceDataset<any, any>>;
}) {
setTimeScale(chartState.range);
@@ -46,7 +45,7 @@ export function initTimeScale({
}
ids.forEach((id) => {
activeResources().forEach((resource) => resource.fetch(id));
activeDatasets.forEach((dataset) => dataset.fetch(id));
});
}, 100);
@@ -63,6 +62,7 @@ export function initTimeScale({
chartState.range = range;
});
}, 50);
onCleanup(() => clearTimeout(timeout));
}
@@ -27,17 +27,16 @@ const whitespaceDateDataset: (SingleValueData & Numbered)[] = new Array(
};
});
const whitespaceHeightDataset: (WhitespaceData & Numbered)[] = new Array(
840_000,
const heightStart = -100_000;
const whitespaceHeightDataset: (SingleValueData & Numbered)[] = new Array(
1_200_000,
)
.fill(0)
.map(
(_, index) =>
({
time: index,
number: index,
}) as any,
);
.map((_, index) => ({
time: (heightStart + index) as any,
number: heightStart + index,
value: NaN,
}));
export function setWhitespace(chart: IChartApi, scale: ResourceScale) {
const whitespaceSeries = createLineSeries(chart);
@@ -1,10 +1,11 @@
import { PRICE_SCALE_MOMENTUM_ID } from "../../chart/price";
import { defaultSeriesOptions } from "./options";
type HistogramOptions = DeepPartial<
HistogramStyleOptions & SeriesOptionsCommon
>;
export const PRICE_SCALE_MOMENTUM_ID = "momentum";
export const createHistogramSeries = (
chart: IChartApi,
options?: HistogramOptions,
+292 -39
View File
@@ -1,19 +1,22 @@
import { createRWS } from "/src/solid/rws";
import { createChart } from "../lightweightCharts/chart/create";
import { applyPriceSeries } from "../lightweightCharts/chart/price";
import { chartState } from "../lightweightCharts/chart/state";
import { initTimeScale } from "../lightweightCharts/chart/time";
import { setWhitespace } from "../lightweightCharts/chart/whitespace";
import { createAreaSeries } from "../lightweightCharts/series/creators/area";
import {
createBaseLineSeries,
DEFAULT_BASELINE_COLORS,
} from "../lightweightCharts/series/creators/baseLine";
import { createCandlesticksSeries } from "../lightweightCharts/series/creators/candlesticks";
import { createHistogramSeries } from "../lightweightCharts/series/creators/histogram";
import { createSeriesLegend } from "../lightweightCharts/series/creators/legend";
import { createLineSeries } from "../lightweightCharts/series/creators/line";
import { colors } from "../utils/colors";
import { debounce } from "../utils/debounce";
import { stringToId } from "../utils/id";
import { webSockets } from "../ws";
export enum SeriesType {
Normal,
@@ -24,7 +27,7 @@ export enum SeriesType {
type SeriesConfig<Scale extends ResourceScale> =
| {
dataset: AnyDataset<Scale>;
dataset: ResourceDataset<Scale>;
color?: string;
colors?: undefined;
seriesType: SeriesType.Based;
@@ -33,7 +36,7 @@ type SeriesConfig<Scale extends ResourceScale> =
defaultVisible?: boolean;
}
| {
dataset: AnyDataset<Scale>;
dataset: ResourceDataset<Scale>;
color?: string;
colors?: string[];
seriesType: SeriesType.Histogram;
@@ -42,7 +45,7 @@ type SeriesConfig<Scale extends ResourceScale> =
defaultVisible?: boolean;
}
| {
dataset: AnyDataset<Scale>;
dataset: ResourceDataset<Scale>;
color: string;
colors?: undefined;
seriesType?: SeriesType.Normal | SeriesType.Area;
@@ -61,20 +64,18 @@ export function applySeriesList<Scale extends ResourceScale>({
datasets,
priceDataset,
priceOptions,
activeResources,
legendSetter,
}: {
charts: RWS<IChartApi[]>;
parentDiv: HTMLDivElement;
preset: Preset;
legendSetter: Setter<PresetLegend>;
priceDataset?: AnyDataset<Scale>;
priceDataset?: ResourceDataset<Scale>;
priceOptions?: PriceSeriesOptions;
priceScaleOptions?: DeepPartialPriceScaleOptions;
top?: SeriesConfig<Scale>[];
bottom?: SeriesConfig<Scale>[];
datasets: Datasets;
activeResources: Accessor<Set<ResourceDataset<any, any>>>;
}) {
reactiveChartList.set((charts) => {
charts.forEach((chart) => {
@@ -92,7 +93,9 @@ export function applySeriesList<Scale extends ResourceScale>({
const priceSeriesType = createRWS<"Candlestick" | "Line">("Candlestick");
const valuesSkipped = createRWS(0);
// const valuesSkipped = createRWS(0);
const activeDatasets: Set<ResourceDataset<any, any>> = new Set();
const charts = [top || [], bottom]
.flatMap((list) => (list ? [list] : []))
@@ -136,17 +139,20 @@ export function applySeriesList<Scale extends ResourceScale>({
const _legendList: PresetLegend = [];
if (index === 0) {
const dataset =
priceDataset ||
(datasets[preset.scale as Scale].price as unknown as NonNullable<
typeof priceDataset
>);
activeDatasets.add(dataset);
const price = applyPriceSeries({
chart,
preset,
seriesType: priceSeriesType,
valuesSkipped,
dataset:
priceDataset ||
(datasets[preset.scale as Scale].price as unknown as NonNullable<
typeof priceDataset
>),
activeResources,
// valuesSkipped,
dataset,
options: priceOptions,
});
@@ -168,6 +174,8 @@ export function applySeriesList<Scale extends ResourceScale>({
options,
defaultVisible,
}) => {
activeDatasets.add(dataset);
let series: ISeriesApi<
"Baseline" | "Line" | "Area" | "Histogram"
>;
@@ -216,10 +224,10 @@ export function applySeriesList<Scale extends ResourceScale>({
);
createEffect(() => {
series.setData(
dataset?.values() || [],
// computeDrawnSeriesValues(dataset?.values(), valuesSkipped()),
);
const values = dataset.values();
console.log(values.length);
series.setData(values);
});
return series;
@@ -275,11 +283,13 @@ export function applySeriesList<Scale extends ResourceScale>({
const ratio = (range.to - range.from) / width;
if (ratio <= 0.5) {
// valuesSkipped.set(0);
priceSeriesType.set("Candlestick");
} else {
priceSeriesType.set("Line");
valuesSkipped.set(Math.floor(ratio / 5));
// valuesSkipped.set(Math.floor(ratio / 1.25));
}
} catch {}
}
@@ -289,6 +299,10 @@ export function applySeriesList<Scale extends ResourceScale>({
50,
);
initTimeScale({
activeDatasets,
});
charts.forEach(({ chart }, index) => {
chart.timeScale().subscribeVisibleLogicalRangeChange((timeRange) => {
// Last chart otherwise length of timescale is Infinity
@@ -327,31 +341,270 @@ export function applySeriesList<Scale extends ResourceScale>({
reactiveChartList.set(() => charts.map(({ chart }) => chart));
}
export function computeDrawnSeriesValues<T>(
values: DatasetValue<T>[] | undefined,
valuesSkipped: number,
) {
values = values || [];
function applyPriceSeries<
Scale extends ResourceScale,
T extends OHLC | number,
>({
chart,
preset,
dataset,
seriesType,
// valuesSkipped,
options,
}: {
chart: IChartApi;
preset: Preset;
// valuesSkipped: Accessor<number>;
seriesType: Accessor<"Candlestick" | "Line">;
dataset: ResourceDataset<Scale, T>;
options?: PriceSeriesOptions;
}) {
// console.time("series add");
if (valuesSkipped === 0) {
return values;
} else {
const valuesSkippedPlus1 = valuesSkipped + 1;
const id = options?.id || "price";
const title = options?.title || "Price";
// console.log(_valuesSkippedPlus1);
const url = "url" in dataset ? (dataset as any).url : undefined;
let length = Math.floor(values.length / valuesSkippedPlus1);
const priceScaleOptions: DeepPartialPriceScaleOptions = {
mode: 1,
...options?.priceScaleOptions,
};
// console.log(length);
let [ohlcSeries, ohlcColors] = createCandlesticksSeries(chart, options);
const filteredValues = new Array(length);
const ohlcLegend = createSeriesLegend({
id,
presetId: preset.id,
title,
color: () => ohlcColors,
series: ohlcSeries,
disabled: () => seriesType() !== "Candlestick",
url,
});
for (let i = 0; i < length; i++) {
filteredValues[i] = values[i * valuesSkippedPlus1];
ohlcSeries.priceScale().applyOptions(priceScaleOptions);
// ---
const lineColor = colors.white;
let lineSeries = createLineSeries(chart, {
color: lineColor,
...options?.seriesOptions,
});
const lineLegend = createSeriesLegend({
id,
presetId: preset.id,
title,
color: () => lineColor,
series: lineSeries,
disabled: () => seriesType() !== "Line",
visible: ohlcLegend.visible,
url,
});
lineSeries.priceScale().applyOptions(priceScaleOptions);
// console.timeEnd("series add");
// lineSeries.setData(whitespaceHeightDataset);
// ohlcSeries.setData({ time: 0, value: NaN });
// ---
// setMinMaxMarkers({
// scale: preset.scale,
// candlesticks:
// dataset?.values() || datasets[preset.scale].price.values() || ([] as any),
// range: chartState.range,
// lowerOpacity,
// });
// const startingValue = {
// number: -1,
// time: -1,
// open: NaN,
// high: NaN,
// low: NaN,
// close: NaN,
// value: NaN,
// };
// lineSeries.update(startingValue);
// ohlcSeries.update(startingValue);
// const callback = (
// chunks: any[],
// valuesSkippedPlus1: number,
// length: number,
// ) => {
// console.time("t");
// console.time("a");
// // chart.removeSeries(ohlcSeries);
// // chart.removeSeries(lineSeries);
// // ohlcSeries = createCandlesticksSeries(chart, options)[0];
// // ohlcSeries.priceScale().applyOptions(priceScaleOptions);
// // lineSeries = createLineSeries(chart, {
// // color: lineColor,
// // ...options?.seriesOptions,
// // });
// // lineSeries.priceScale().applyOptions(priceScaleOptions);
// const values = new Array(length);
// let i = 0;
// for (let k = 0; k < chunks.length; k++) {
// const chunk = chunks[k];
// // const chunk =
// // fetchedJSONs[chunkIdToIndex(dataset.scale, activeRange[k])]?.vec?.() ||
// // [];
// for (let j = 0; j < chunk.length; j += valuesSkippedPlus1) {
// values[i++] = chunk[j];
// // console.log(chunk[j]);
// // callback(chunk[j]);
// // for (let i = 0; i < seriesList.length; i++) {
// // seriesList[i].update(chunk[j]);
// // }
// // const value = chunk[j];
// // console.log(value.time);
// // lineSeries.update(value);
// // ohlcSeries.update(value);
// // i++;
// }
// }
// console.log(values.length);
// console.timeEnd("t");
// lineSeries.setData(values);
// console.timeEnd("a");
// };
// const debouncedCallback = debounce(callback, 200);
createEffect(() => {
const values = dataset.values();
console.log(values.length);
lineSeries.setData(values);
ohlcSeries.setData(values);
});
// createEffect(() =>
// computeDrawnSeriesValues(
// dataset,
// valuesSkipped(),
// debouncedCallback,
// // [lineSeries, ohlcSeries],
// // (value) => {
// // try {
// // console.log(value);
// // lineSeries.update(value);
// // ohlcSeries.update(value);
// // } catch {}
// // }),
// ),
// );
createEffect(() => {
if (preset.scale === "date") {
const latest = webSockets.liveKrakenCandle.latest();
if (latest) {
ohlcSeries.update(latest);
lineSeries.update(latest);
}
}
});
// console.log(filteredValues.length);
return filteredValues;
}
return { ohlcLegend, lineLegend };
}
// // const computeDrawnSeriesValues = debounce(_computeDrawnSeriesValues, 100);
// function computeDrawnSeriesValues<
// S extends ResourceScale,
// T extends OHLC | number,
// >(
// dataset: ResourceDataset<S, T>,
// valuesSkipped: number,
// callback: (chunks: any, v: number, l: number) => void,
// // seriesList: ISeriesApi<any>[],
// ) {
// // console.time(dataset.url);
// const { fetchedJSONs, activeRange: _activeRange } = dataset;
// const activeRange = _activeRange();
// const valuesSkippedPlus1 = valuesSkipped + 1;
// if (valuesSkippedPlus1 === 1) {
// console.log("todo valuesSkippedPlus1===1, skip for now");
// }
// // for (let i = 0; i < seriesList.length; i++) {
// // seriesList[i].
// // }
// const chunks = new Array(activeRange.length);
// let length = 0;
// for (let i = 0; i < chunks.length; i++) {
// const chunk =
// fetchedJSONs[chunkIdToIndex(dataset.scale, activeRange[i])]?.vec?.() ||
// [];
// chunks[i] = chunk;
// length += Math.ceil(chunk.length / valuesSkippedPlus1);
// }
// callback(chunks, valuesSkippedPlus1, length);
// // setValues(chunks, valuesSkippedPlus1, length, callback);
// // }
// // // const debouncedSetValues = debounce(setValues, 50);
// // function setValues(
// // chunks: any[],
// // valuesSkippedPlus1: number,
// // length: number,
// // callback: (values: any[]) => void,
// // ) {
// // const values = new Array(length);
// // let i = 0;
// // for (let k = 0; k < activeRange.length; k++) {
// // const chunk = chunks[k];
// // // const chunk =
// // // fetchedJSONs[chunkIdToIndex(dataset.scale, activeRange[k])]?.vec?.() ||
// // // [];
// // for (let j = 0; j < chunk.length; j += valuesSkippedPlus1) {
// // // values[i++] = chunk[j];
// // // console.log(chunk[j]);
// // // callback(chunk[j]);
// // for (let i = 0; i < seriesList.length; i++) {
// // seriesList[i].update(chunk[j]);
// // }
// // // i++;
// // }
// // }
// // console.log(i);
// // if (i !== values.length) {
// // console.log({ n: i, values });
// // throw Error("error");
// // }
// // console.timeEnd(dataset.url);
// }
-1
View File
@@ -24,7 +24,6 @@ type ApplyPreset = (params: {
parentDiv: HTMLDivElement;
datasets: Datasets;
preset: Preset;
activeResources: Accessor<Set<ResourceDataset<any, any>>>;
legendSetter: Setter<PresetLegend>;
}) => void;
+1
View File
@@ -110,6 +110,7 @@ export const convertCandleToVolumeColor = (
export const colors = {
white,
black,
darkWhite,
gray,
lightBitcoin: yellow,
+7 -7
View File
@@ -1061,12 +1061,12 @@ dependencies = [
[[package]]
name = "memory-stats"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34f79cf9964c5c9545493acda1263f1912f8d2c56c8a2ffee2606cb960acaacc"
checksum = "c73f5c649995a115e1a0220b35e4df0a1294500477f97a91d0660fb5abeb574a"
dependencies = [
"libc",
"winapi",
"windows-sys 0.52.0",
]
[[package]]
@@ -1209,9 +1209,9 @@ dependencies = [
[[package]]
name = "ordered-float"
version = "4.2.0"
version = "4.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e"
checksum = "19ff2cf528c6c03d9ed653d6c4ce1dc0582dc4af309790ad92f07c1cd551b0be"
dependencies = [
"num-traits",
]
@@ -1648,9 +1648,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.117"
version = "1.0.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
dependencies = [
"itoa",
"ryu",
+3 -3
View File
@@ -20,12 +20,12 @@ fastrand = "2.1.0"
inferno = "0.11.19"
itertools = "0.13.0"
leveldb = "0.8.6"
memory-stats = "1.1.0"
memory-stats = "1.2.0"
nohash = "0.2.0"
ordered-float = "4.2.0"
ordered-float = "4.2.1"
par-iter-sync = "0.1.11"
rayon = "1.10.0"
reqwest = { version = "0.12.5", features = ["blocking", "json"] }
sanakirja = "1.4.2"
serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.117"
serde_json = "1.0.120"
+13 -13
View File
@@ -1242,12 +1242,12 @@ dependencies = [
[[package]]
name = "memory-stats"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34f79cf9964c5c9545493acda1263f1912f8d2c56c8a2ffee2606cb960acaacc"
checksum = "c73f5c649995a115e1a0220b35e4df0a1294500477f97a91d0660fb5abeb574a"
dependencies = [
"libc",
"winapi",
"windows-sys 0.52.0",
]
[[package]]
@@ -1390,9 +1390,9 @@ dependencies = [
[[package]]
name = "ordered-float"
version = "4.2.0"
version = "4.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e"
checksum = "19ff2cf528c6c03d9ed653d6c4ce1dc0582dc4af309790ad92f07c1cd551b0be"
dependencies = [
"num-traits",
]
@@ -1601,9 +1601,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.4"
version = "1.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
dependencies = [
"aho-corasick",
"memchr",
@@ -1874,9 +1874,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.117"
version = "1.0.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
dependencies = [
"itoa",
"ryu",
@@ -2114,9 +2114,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.37.0"
version = "1.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
dependencies = [
"backtrace",
"bytes",
@@ -2133,9 +2133,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "2.2.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
dependencies = [
"proc-macro2",
"quote",
+5 -5
View File
@@ -7,12 +7,12 @@ edition = "2021"
axum = "0.7.5"
color-eyre = "0.6.3"
itertools = "0.12.1"
regex = "1.10.4"
regex = "1.10.5"
bincode = { git = "https://github.com/bincode-org/bincode.git" }
reqwest = { version = "0.12.4", features = ["json"] }
serde = { version = "1.0.199", features = ["derive"] }
serde_json = { version = "1.0.116" }
tokio = { version = "1.37.0", features = ["full"] }
reqwest = { version = "0.12.5", features = ["json"] }
serde = { version = "1.0.203", features = ["derive"] }
serde_json = { version = "1.0.120" }
tokio = { version = "1.38.0", features = ["full"] }
tower-http = { version = "0.5.2", features = ["compression-full"] }
parser = { path = "../parser" }
derive_deref = "1.1.1"