el.scrollWidth > el.clientWidth);
-
- checkArrows();
+ checkScrollable();
});
});
+ function checkScrollable() {
+ const div = maybeScrollable();
+
+ if (div) {
+ scrollable.set(() => div.scrollWidth > div.clientWidth);
+ }
+
+ checkArrows();
+ }
+
function checkArrows() {
const target = maybeScrollable()!;
@@ -38,6 +46,9 @@ export function Scrollable({
showRightArrow.set(() => right > 0);
}
+ // @ts-ignore
+ createEffect(on(children, checkScrollable));
+
return (
;
backgroundOpacity: SL<{ text: string; value: number }>;
}) {
- createEffect(() => {
- if (
- appTheme.selected() === "Dark" ||
- (appTheme.selected() === "System" &&
- window.matchMedia("(prefers-color-scheme: dark)").matches)
- ) {
- document.documentElement.classList.add("dark");
- } else {
- document.documentElement.classList.remove("dark");
- }
- });
-
return (
-
- And other stuff NOT transmitted by
- relays.
-
+
@@ -74,7 +59,13 @@ export function SettingsFrame({
- Version: {version}
+ Version: {" "}
+
+ {version}
+
diff --git a/app/src/app/index.tsx b/app/src/app/index.tsx
index 1d38cb3d9..5e222ccd5 100644
--- a/app/src/app/index.tsx
+++ b/app/src/app/index.tsx
@@ -2,8 +2,6 @@ import { createRWS } from "/src/solid/rws";
import { standalone } from "../env";
import { createDatasets } from "../scripts/datasets";
-import { chartState } from "../scripts/lightweightCharts/chart/state";
-import { setTimeScale } from "../scripts/lightweightCharts/chart/time";
import { createPresets } from "../scripts/presets";
import { createSL } from "../scripts/utils/selectableList/static";
import { sleep } from "../scripts/utils/sleep";
@@ -41,6 +39,22 @@ export function App() {
defaultIndex: 0,
});
+ const dark = createRWS(false);
+
+ createEffect(() => {
+ if (
+ appTheme.selected() === "Dark" ||
+ (appTheme.selected() === "System" &&
+ window.matchMedia("(prefers-color-scheme: dark)").matches)
+ ) {
+ dark.set(true);
+ document.documentElement.classList.add("dark");
+ } else {
+ dark.set(false);
+ document.documentElement.classList.remove("dark");
+ }
+ });
+
const backgroundMode = createSL(["Scroll", "Static"] as const, {
saveable: {
key: "bg-mode",
@@ -222,8 +236,6 @@ export function App() {
windowWidth60p(),
),
);
-
- setTimeScale(resizeInitialRange());
}
}}
onMouseUp={() => resizingBarStart.set(undefined)}
@@ -267,6 +279,7 @@ export function App() {
qrcode={qrcode}
standalone={false}
datasets={datasets}
+ dark={dark}
/>
@@ -303,16 +316,12 @@ export function App() {
{
- resizeInitialRange.set(chartState.range);
-
if (resizingBarStart() === undefined) {
resizingBarStart.set(event.clientX);
resizingBarWidth.set(barWidth());
}
}}
onTouchStart={(event) => {
- resizeInitialRange.set(chartState.range);
-
if (resizingBarStart() === undefined) {
resizingBarStart.set(event.touches[0].clientX);
resizingBarWidth.set(barWidth());
@@ -320,8 +329,6 @@ export function App() {
}}
onDblClick={() => {
barWidth.set(0);
-
- setTimeScale(resizeInitialRange());
}}
/>
@@ -334,6 +341,7 @@ export function App() {
qrcode={qrcode}
fullscreen={fullscreen}
datasets={datasets}
+ dark={dark}
/>
diff --git a/app/src/scripts/datasets/consts/types.d.ts b/app/src/scripts/datasets/consts/types.d.ts
index a9357aeeb..3a38ac7ae 100644
--- a/app/src/scripts/datasets/consts/types.d.ts
+++ b/app/src/scripts/datasets/consts/types.d.ts
@@ -9,7 +9,10 @@ type AddressCohortKeySplitByLiquidity = `${LiquidityKey}_${AddressCohortKey}`;
type AnyCohortKey = AgeCohortKey | AddressCohortKey;
-type AnyPossibleCohortKey = AnyCohortKey | AddressCohortKeySplitByLiquidity;
+type AnyPossibleCohortKey =
+ | AnyCohortKey
+ | AddressCohortKeySplitByLiquidity
+ | LiquidityKey;
type AverageName = (typeof import("./averages").averages)[number]["key"];
diff --git a/app/src/scripts/datasets/resource.ts b/app/src/scripts/datasets/resource.ts
index 1c42a1efa..7751ed11e 100644
--- a/app/src/scripts/datasets/resource.ts
+++ b/app/src/scripts/datasets/resource.ts
@@ -1,12 +1,7 @@
-import {
- ONE_DAY_IN_MS,
- ONE_HOUR_IN_MS,
- ONE_MINUTE_IN_MS,
-} from "/src/scripts/utils/time";
+import { ONE_HOUR_IN_MS, ONE_MINUTE_IN_MS } from "/src/scripts/utils/time";
import { createRWS } from "/src/solid/rws";
import { HEIGHT_CHUNK_SIZE } from ".";
-import { debounce } from "../utils/debounce";
export function createResourceDataset<
Scale extends ResourceScale,
@@ -49,21 +44,24 @@ export function createResourceDataset<
const chunkId = json()?.chunk.id!;
if (Array.isArray(map)) {
- return map.map(
- (value, index) =>
- ({
- number: chunkId + index,
- time: (chunkId + index) as Time,
- ...(typeof value !== "number" && value !== null
- ? { ...(value as OHLC), value: value.close }
- : { value: value === null ? NaN : (value as number) }),
- }) as any as Value,
- );
+ const values = new Array(map.length);
+
+ for (let i = 0; i < map.length; i++) {
+ const value = map[i];
+
+ values[i] = {
+ time: (chunkId + i) as Time,
+ ...(typeof value !== "number" && value !== null
+ ? { ...(value as OHLC), value: value.close }
+ : { value: value === null ? NaN : (value as number) }),
+ } as any as Value;
+ }
+
+ return values;
} else {
return Object.entries(map).map(
([date, value]) =>
({
- number: new Date(date).valueOf() / ONE_DAY_IN_MS,
time: date,
...(typeof value !== "number" && value !== null
? { ...(value as OHLC), value: value.close }
@@ -89,10 +87,18 @@ export function createResourceDataset<
const fetched = fetchedJSONs.at(index);
+ if (scale === "height" && index > 0) {
+ const length = fetchedJSONs.at(index - 1)?.vec()?.length;
+
+ if (length !== undefined && length < HEIGHT_CHUNK_SIZE) {
+ return;
+ }
+ }
+
if (!fetched || fetched.loading) {
return;
} else if (fetched.at) {
- const diff = new Date().valueOf() - fetched.at.valueOf();
+ const diff = new Date().getTime() - fetched.at.getTime();
if (
diff < ONE_MINUTE_IN_MS ||
@@ -127,6 +133,11 @@ export function createResourceDataset<
} catch {}
}
+ if (!navigator.onLine) {
+ fetched.loading = false;
+ return;
+ }
+
try {
const fetchedResponse = await fetch(urlWithQuery);
@@ -153,7 +164,7 @@ export function createResourceDataset<
return;
}
- if (previousLength && previousLength <= newLength) {
+ if (previousLength && previousLength === newLength) {
const previousLastValue = Object.values(previousMap || []).at(-1);
const newLastValue = Object.values(newMap).at(-1);
@@ -195,43 +206,11 @@ 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([]);
-
- createEffect(() => {
- const vecs = fetchedJSONs.map((fetched) => fetched.vec() || []);
- debouncedValuesCallback(vecs);
- });
-
const resource: ResourceDataset = {
scale,
url: baseURL,
fetch: _fetch,
fetchedJSONs,
- values,
drop() {
fetchedJSONs.forEach((fetched) => {
fetched.at = null;
@@ -254,6 +233,6 @@ async function convertResponseToJSON<
}
}
-function chunkIdToIndex(scale: ResourceScale, id: number) {
+export function chunkIdToIndex(scale: ResourceScale, id: number) {
return scale === "date" ? id - 2009 : Math.floor(id / HEIGHT_CHUNK_SIZE);
}
diff --git a/app/src/scripts/datasets/types.d.ts b/app/src/scripts/datasets/types.d.ts
index 5230cfbfc..d1f8ac587 100644
--- a/app/src/scripts/datasets/types.d.ts
+++ b/app/src/scripts/datasets/types.d.ts
@@ -6,7 +6,7 @@ type AnyDatasets = DateDatasets | HeightDatasets;
type ResourceScale = (typeof import("./index").scales)[index];
-type DatasetValue = T & Numbered & Valued;
+type DatasetValue = T & Valued;
interface ResourceDataset<
Scale extends ResourceScale,
@@ -24,7 +24,6 @@ interface ResourceDataset<
url: string;
fetch: (id: number) => void;
fetchedJSONs: FetchedResult[];
- values: Accessor[]>;
drop: VoidFunction;
}
diff --git a/app/src/scripts/lightweightCharts/chart/create.ts b/app/src/scripts/lightweightCharts/chart/create.ts
index 7c943c2b6..2b4c94402 100644
--- a/app/src/scripts/lightweightCharts/chart/create.ts
+++ b/app/src/scripts/lightweightCharts/chart/create.ts
@@ -11,11 +11,20 @@ import { HorzScaleBehaviorHeight } from "./horzScaleBehavior";
export function createChart(
scale: ResourceScale,
element: HTMLElement,
- priceScaleOptions?: DeepPartialPriceScaleOptions,
+ {
+ dark,
+ priceScaleOptions,
+ }: {
+ dark: boolean;
+ priceScaleOptions: DeepPartialPriceScaleOptions;
+ },
) {
console.log(`chart: create (scale: ${scale})`);
- const { white } = colors;
+ const { white, black } = colors;
+
+ const textColor = dark ? white : black;
+ const borderColor = dark ? "#332F24" : "#F1E4E0";
const options: DeepPartialChartOptions = {
autoSize: true,
@@ -23,18 +32,18 @@ export function createChart(
fontFamily: "Lexend",
background: { color: "transparent" },
fontSize: 14,
- textColor: white,
+ textColor,
},
grid: {
vertLines: { visible: false },
horzLines: { visible: false },
},
rightPriceScale: {
- borderColor: "#332F24",
+ borderColor,
},
timeScale: {
- borderColor: "#332F24",
- minBarSpacing: scale === "date" ? 0.05 : 0.005,
+ borderColor,
+ minBarSpacing: 0.05,
shiftVisibleRangeOnNewBar: false,
allowShiftVisibleRangeOnWhitespaceReplacement: false,
},
@@ -46,12 +55,12 @@ export function createChart(
crosshair: {
mode: CrosshairMode.Normal,
horzLine: {
- color: white,
- labelBackgroundColor: white,
+ color: textColor,
+ labelBackgroundColor: textColor,
},
vertLine: {
- color: white,
- labelBackgroundColor: white,
+ color: textColor,
+ labelBackgroundColor: textColor,
},
},
localization: {
diff --git a/app/src/scripts/lightweightCharts/chart/markers.ts b/app/src/scripts/lightweightCharts/chart/markers.ts
index 6c1d5500d..e27309c23 100644
--- a/app/src/scripts/lightweightCharts/chart/markers.ts
+++ b/app/src/scripts/lightweightCharts/chart/markers.ts
@@ -1,121 +1,128 @@
import { colors } from "/src/scripts/utils/colors";
-import { ONE_DAY_IN_MS } from "/src/scripts/utils/time";
-import { chartState } from "./state";
-import { GENESIS_DAY } from "./whitespace";
+import { chunkIdToIndex } from "../../datasets/resource";
-export const setMinMaxMarkers = ({
+export function setMinMaxMarkers({
scale,
- candlesticks,
- range,
- lowerOpacity,
+ visibleRange,
+ legendList,
+ activeRange,
}: {
scale: ResourceScale;
- candlesticks: DatasetValue[];
- range: TimeRange;
- lowerOpacity: boolean;
-}) => {
- const first = candlesticks.at(0);
+ visibleRange: TimeRange | undefined;
+ legendList: SeriesLegend[];
+ activeRange: Accessor;
+}) {
+ if (!visibleRange) return;
- if (!first) return;
+ const { from, to } = visibleRange;
- const offset =
- scale === "date"
- ? first.number - new Date(GENESIS_DAY).valueOf() / ONE_DAY_IN_MS
- : 0;
+ const dateFrom = new Date(from as string);
+ const dateTo = new Date(to as string);
- const slicedDataList = range
- ? candlesticks.slice(
- Math.ceil(range.from - offset < 0 ? 0 : range.from - offset),
- Math.floor(range.to - offset) + 1,
- )
- : [];
+ let max = undefined as [number, Time, number, ISeriesApi] | undefined;
+ let min = undefined as [number, Time, number, ISeriesApi] | undefined;
- const series = chartState.priceSeries;
+ legendList.forEach(({ seriesList, dataset }) => {
+ activeRange().forEach((id) => {
+ const seriesIndex = chunkIdToIndex(scale, id);
- if (!series) return;
+ const series = seriesList.at(seriesIndex)?.();
- if (slicedDataList.length) {
- const markers: (SeriesMarker & Numbered)[] = [];
+ if (!series || !series?.options().visible) return;
- const seriesIsCandlestick = series.seriesType() === "Candlestick";
+ series.setMarkers([]);
- [
- {
- mathFunction: "min" as const,
- placementAttribute: seriesIsCandlestick
- ? ("low" as const)
- : ("close" as const),
- // valueAttribute: 'low' as const,
- markerOptions: {
- position: "belowBar" as const,
- shape: "arrowUp" as const,
- },
- },
- {
- mathFunction: "max" as const,
- placementAttribute: seriesIsCandlestick
- ? ("high" as const)
- : ("close" as const),
- // valueAttribute: 'high' as const,
- markerOptions: {
- position: "aboveBar" as const,
- shape: "arrowDown" as const,
- },
- },
- ].map(
- ({
- mathFunction,
- placementAttribute,
- // valueAttribute,
- markerOptions,
- }) => {
- const value = Math[mathFunction](
- // ...slicedDataList.map((data) => data[valueAttribute] || 0),
- ...slicedDataList.map(
- (data) =>
- (placementAttribute in data
- ? data[placementAttribute]
- : data.value) || 0,
- ),
- );
+ const isCandlestick = series.seriesType() === "Candlestick";
- const placement = Math[mathFunction](
- ...slicedDataList.map(
- (data) =>
- (placementAttribute in data
- ? data[placementAttribute]
- : data.value) || 0,
- ),
- );
+ const vec = dataset.fetchedJSONs.at(seriesIndex)?.vec();
- const candle = slicedDataList.find(
- (data) =>
- (placementAttribute in data
- ? data[placementAttribute]
- : data.value) === placement,
- );
+ if (!vec) return;
- return (
- candle &&
- markers.push({
- ...markerOptions,
- number: candle.number,
- time: candle.time,
- color: lowerOpacity ? colors.darkWhite : colors.white,
- size: 0,
- text: value.toLocaleString("en-us"),
- })
- );
- },
- );
+ for (let i = 0; i < vec.length; i++) {
+ const data = vec[i];
- series.setMarkers(sortWhitespaceDataArray(markers));
+ let number;
+
+ if (scale === "date") {
+ const date = new Date(
+ typeof data.time === "string"
+ ? data.time
+ : // @ts-ignore
+ `${data.time.year}-${data.time.month}-${data.time.day}`,
+ );
+
+ number = date.getTime();
+
+ if (date <= dateFrom || date >= dateTo) {
+ continue;
+ }
+ } else {
+ const height = data.time;
+
+ number = height as number;
+
+ if (height <= from || height >= to) {
+ continue;
+ }
+ }
+
+ // @ts-ignore
+ const high = isCandlestick ? data["high"] : data.value;
+ // @ts-ignore
+ const low = isCandlestick ? data["low"] : data.value;
+
+ if (!max || high > max[2]) {
+ max = [number, data.time, high, series];
+ }
+ if (!min || low < min[2]) {
+ min = [number, data.time, low, series];
+ }
+ }
+ });
+ });
+
+ let minMarker: (SeriesMarker & { weight: number }) | undefined;
+ let maxMarker: (SeriesMarker & { weight: number }) | undefined;
+
+ if (min) {
+ minMarker = {
+ weight: min[0],
+ time: min[1],
+ color: colors.white,
+ position: "belowBar" as const,
+ shape: "arrowUp" as const,
+ size: 0,
+ text: min[2].toLocaleString("en-us"),
+ };
}
-};
-function sortWhitespaceDataArray(
- array: T[],
-) {
- return array.sort(({ number: a }, { number: b }) => a - b);
+ if (max) {
+ maxMarker = {
+ weight: max[0],
+ time: max[1],
+ color: colors.white,
+ position: "aboveBar" as const,
+ shape: "arrowDown" as const,
+ size: 0,
+ text: max[2].toLocaleString("en-us"),
+ };
+ }
+
+ if (min && max && min[3] === max[3] && minMarker && maxMarker) {
+ const series = min[3];
+ series.setMarkers(
+ [minMarker, maxMarker].sort((a, b) => a.weight - b.weight),
+ );
+ } else {
+ if (min && minMarker) {
+ const series = min[3];
+ series.setMarkers([minMarker]);
+ }
+
+ if (max && maxMarker) {
+ const series = max[3];
+ series.setMarkers([maxMarker]);
+ }
+ }
}
diff --git a/app/src/scripts/lightweightCharts/chart/state.ts b/app/src/scripts/lightweightCharts/chart/state.ts
deleted file mode 100644
index 7d97001c8..000000000
--- a/app/src/scripts/lightweightCharts/chart/state.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { getInitialRange } from "./time";
-
-export const LOCAL_STORAGE_RANGE_KEY = "chart-range";
-export const URL_PARAMS_RANGE_FROM_KEY = "from";
-export const URL_PARAMS_RANGE_TO_KEY = "to";
-
-export const chartState = {
- chart: null as IChartApi | null,
- range: getInitialRange(),
-};
diff --git a/app/src/scripts/lightweightCharts/chart/time.ts b/app/src/scripts/lightweightCharts/chart/time.ts
index 6ebfe03f3..96d8dfd63 100644
--- a/app/src/scripts/lightweightCharts/chart/time.ts
+++ b/app/src/scripts/lightweightCharts/chart/time.ts
@@ -1,108 +1,157 @@
import { HEIGHT_CHUNK_SIZE } from "../../datasets";
import { debounce } from "../../utils/debounce";
import { writeURLParam } from "../../utils/urlParams";
-import {
- chartState,
- LOCAL_STORAGE_RANGE_KEY,
- URL_PARAMS_RANGE_FROM_KEY,
- URL_PARAMS_RANGE_TO_KEY,
-} from "./state";
-const debouncedUpdateURLParams = debounce((range: TimeRange | null) => {
- if (!range) return;
-
- writeURLParam(URL_PARAMS_RANGE_FROM_KEY, String(range.from));
-
- writeURLParam(URL_PARAMS_RANGE_TO_KEY, String(range.to));
-
- localStorage.setItem(LOCAL_STORAGE_RANGE_KEY, JSON.stringify(range));
-}, 500);
+const LOCAL_STORAGE_RANGE_KEY = "chart-range";
+const URL_PARAMS_RANGE_FROM_KEY = "from";
+const URL_PARAMS_RANGE_TO_KEY = "to";
export function initTimeScale({
- activeDatasets,
+ scale,
+ activeRange,
+ exactRange,
+ charts,
}: {
- activeDatasets: Set>;
+ scale: ResourceScale;
+ activeRange: RWS;
+ exactRange: RWS;
+ charts: ChartObject[];
}) {
- setTimeScale(chartState.range);
+ const firstChart = charts.at(0)?.chart;
- const debouncedFetch = debounce((range: TimeRange | null) => {
+ if (!firstChart) return;
+
+ firstChart.timeScale().subscribeVisibleTimeRangeChange((range) => {
if (!range) return;
- let ids: number[] = [];
+ exactRange.set(range);
- if (typeof range.from === "string" && typeof range.to === "string") {
- const from = new Date(range.from).getUTCFullYear();
- const to = new Date(range.to).getUTCFullYear();
+ debouncedSetActiveRange({ range, activeRange });
- ids = Array.from({ length: to - from + 1 }, (_, i) => i + from);
- } else {
- const from = Math.floor(Number(range.from) / HEIGHT_CHUNK_SIZE);
- const to = Math.floor(Number(range.to) / HEIGHT_CHUNK_SIZE);
+ debouncedSaveTimeRange({ scale, range });
+ });
- const length = to - from + 1;
-
- ids = Array.from({ length }, (_, i) => (from + i) * HEIGHT_CHUNK_SIZE);
- }
-
- ids.forEach((id) => {
- activeDatasets.forEach((dataset) => dataset.fetch(id));
- });
- }, 100);
-
- debouncedFetch(chartState.range);
-
- let timeout = setTimeout(() => {
- chartState.chart?.timeScale().subscribeVisibleTimeRangeChange((range) => {
- debouncedFetch(range);
-
- debouncedUpdateURLParams(range);
-
- range = range || chartState.range;
-
- chartState.range = range;
- });
- }, 50);
-
- onCleanup(() => clearTimeout(timeout));
+ setTimeScale(firstChart, getInitialRange(scale));
}
-export function getInitialRange(): TimeRange {
+function setTimeScale(chart: IChartApi, range: TimeRange | null) {
+ if (range) {
+ setTimeout(() => {
+ chart.timeScale().setVisibleRange(range);
+ }, 1);
+ }
+}
+
+function getInitialRange(scale: ResourceScale): TimeRange {
const urlParams = new URLSearchParams(window.location.search);
const urlFrom = urlParams.get(URL_PARAMS_RANGE_FROM_KEY);
const urlTo = urlParams.get(URL_PARAMS_RANGE_TO_KEY);
if (urlFrom && urlTo) {
- return {
- from: urlFrom,
- to: urlTo,
- } satisfies TimeRange;
+ if (scale === "date" && urlFrom.includes("-") && urlTo.includes("-")) {
+ return {
+ from: new Date(urlFrom).toJSON().split("T")[0],
+ to: new Date(urlTo).toJSON().split("T")[0],
+ } satisfies TimeRange;
+ } else if (
+ scale === "height" &&
+ !urlFrom.includes("-") &&
+ !urlTo.includes("-")
+ ) {
+ return {
+ from: Number(urlFrom),
+ to: Number(urlTo),
+ } as any satisfies TimeRange;
+ }
}
const savedTimeRange = JSON.parse(
- localStorage.getItem(LOCAL_STORAGE_RANGE_KEY) || "null",
+ localStorage.getItem(getLocalStorageKey(scale)) || "null",
) as TimeRange | null;
if (savedTimeRange) {
return savedTimeRange;
}
- const defaultTo = new Date();
- const defaultFrom = new Date();
- defaultFrom.setDate(defaultFrom.getUTCDate() - 6 * 30);
+ switch (scale) {
+ case "date": {
+ const defaultTo = new Date();
+ const defaultFrom = new Date();
+ defaultFrom.setDate(defaultFrom.getUTCDate() - 6 * 30);
- const defaultTimeRange = {
- from: defaultFrom.toJSON().split("T")[0],
- to: defaultTo.toJSON().split("T")[0],
- } satisfies TimeRange;
-
- return defaultTimeRange;
-}
-
-export function setTimeScale(range: TimeRange | null) {
- if (range) {
- setTimeout(() => {
- chartState.chart?.timeScale().setVisibleRange(range);
- }, 1);
+ return {
+ from: defaultFrom.toJSON().split("T")[0],
+ to: defaultTo.toJSON().split("T")[0],
+ } satisfies TimeRange;
+ }
+ case "height": {
+ return {
+ from: 800_000,
+ to: 850_000,
+ } as any satisfies TimeRange;
+ }
}
}
+
+function getLocalStorageKey(scale: ResourceScale) {
+ return `${LOCAL_STORAGE_RANGE_KEY}-${scale}`;
+}
+
+function setActiveRange({
+ range,
+ activeRange,
+}: {
+ range: TimeRange;
+ activeRange: RWS;
+}) {
+ let ids: number[] = [];
+
+ const today = new Date();
+
+ if (typeof range.from === "string" && typeof range.to === "string") {
+ const from = new Date(range.from).getUTCFullYear();
+ const to = new Date(range.to).getUTCFullYear();
+
+ ids = Array.from({ length: to - from + 1 }, (_, i) => i + from).filter(
+ (year) => year >= 2009 && year <= today.getUTCFullYear(),
+ );
+ } else {
+ const from = Math.floor(Number(range.from) / HEIGHT_CHUNK_SIZE);
+ const to = Math.floor(Number(range.to) / HEIGHT_CHUNK_SIZE);
+
+ const length = to - from + 1;
+
+ ids = Array.from({ length }, (_, i) => (from + i) * HEIGHT_CHUNK_SIZE);
+ }
+
+ const old = activeRange();
+
+ if (
+ old.length !== ids.length ||
+ old.at(0) !== ids.at(0) ||
+ old.at(-1) !== ids.at(-1)
+ ) {
+ console.log("range:", ids);
+
+ activeRange.set(ids);
+ }
+}
+
+const debouncedSetActiveRange = debounce(setActiveRange, 100);
+
+function saveTimeRange({
+ scale,
+ range,
+}: {
+ scale: ResourceScale;
+ range: TimeRange;
+}) {
+ writeURLParam(URL_PARAMS_RANGE_FROM_KEY, String(range.from));
+
+ writeURLParam(URL_PARAMS_RANGE_TO_KEY, String(range.to));
+
+ localStorage.setItem(getLocalStorageKey(scale), JSON.stringify(range));
+}
+
+const debouncedSaveTimeRange = debounce(saveTimeRange, 500);
diff --git a/app/src/scripts/lightweightCharts/chart/whitespace.ts b/app/src/scripts/lightweightCharts/chart/whitespace.ts
index 782112a90..da3b6c0ed 100644
--- a/app/src/scripts/lightweightCharts/chart/whitespace.ts
+++ b/app/src/scripts/lightweightCharts/chart/whitespace.ts
@@ -1,5 +1,4 @@
import { dateToString, getNumberOfDaysBetweenTwoDates } from "../../utils/date";
-import { ONE_DAY_IN_MS } from "../../utils/time";
import { createLineSeries } from "../series/creators/line";
export const DAY_BEFORE_GENESIS_DAY = "2009-01-02";
@@ -9,41 +8,58 @@ export const GENESIS_DAY = "2009-01-03";
const whitespaceStartDate = "1970-01-01";
const whitespaceEndDate = "2100-01-01";
-const whitespaceDateDataset: (SingleValueData & Numbered)[] = new Array(
+const whitespaceDateDataset: (WhitespaceData | SingleValueData)[] = new Array(
getNumberOfDaysBetweenTwoDates(
new Date(whitespaceStartDate),
new Date(whitespaceEndDate),
),
-)
- .fill(0)
- .map((_, index) => {
- const date = new Date(whitespaceStartDate);
- date.setUTCDate(date.getUTCDay() + index);
+);
+// Hack to be able to scroll freely
+// Setting them all to NaN is much slower
+for (let i = 0; i < whitespaceDateDataset.length; i++) {
+ const date = new Date(whitespaceStartDate);
+ date.setUTCDate(date.getUTCDay() + i);
- return {
- number: date.valueOf() / ONE_DAY_IN_MS,
- time: dateToString(date),
+ const time = dateToString(date);
+
+ if (i === whitespaceDateDataset.length - 1) {
+ whitespaceDateDataset[i] = {
+ time,
value: NaN,
};
- });
-
-const heightStart = -100_000;
-const whitespaceHeightDataset: (SingleValueData & Numbered)[] = new Array(
- 1_200_000,
-)
- .fill(0)
- .map((_, index) => ({
- time: (heightStart + index) as any,
- number: heightStart + index,
- value: NaN,
- }));
-
-export function setWhitespace(chart: IChartApi, scale: ResourceScale) {
- const whitespaceSeries = createLineSeries(chart);
-
- if (scale === "date") {
- whitespaceSeries.setData(whitespaceDateDataset);
} else {
- whitespaceSeries.setData(whitespaceHeightDataset);
+ whitespaceDateDataset[i] = {
+ time,
+ };
}
}
+
+const heightStart = -50_000;
+const whitespaceHeightDataset: WhitespaceData[] = new Array(
+ (new Date().getUTCFullYear() - 2009 + 1) * 60_000,
+);
+for (let i = 0; i < whitespaceHeightDataset.length; i++) {
+ const height = heightStart + i;
+
+ whitespaceHeightDataset[i] = {
+ time: height as any,
+ };
+}
+
+export function setWhitespace(chart: IChartApi, scale: ResourceScale) {
+ const whitespace = createLineSeries(chart);
+
+ if (scale === "date") {
+ whitespace.setData(whitespaceDateDataset);
+ } else {
+ whitespace.setData(whitespaceHeightDataset);
+
+ const time = whitespaceHeightDataset.length;
+ whitespace.update({
+ time: time as Time,
+ value: NaN,
+ });
+ }
+
+ return whitespace;
+}
diff --git a/app/src/scripts/lightweightCharts/series/creators/baseLine.ts b/app/src/scripts/lightweightCharts/series/creators/baseLine.ts
index c8d4585fd..0ca94d351 100644
--- a/app/src/scripts/lightweightCharts/series/creators/baseLine.ts
+++ b/app/src/scripts/lightweightCharts/series/creators/baseLine.ts
@@ -26,14 +26,14 @@ export const createBaseLineSeries = (
} = options;
const allTopColor = topColor || color || DEFAULT_BASELINE_TOP_COLOR;
- const topFillColor = `${allTopColor}`;
+ const topFillColor = `transparent`;
const allBottomColor = bottomColor || color || DEFAULT_BASELINE_BOTTOM_COLOR;
- const bottomFillColor = `${allBottomColor}`;
+ const bottomFillColor = `transparent`;
const seriesOptions: DeepPartialBaselineOptions = {
priceScaleId: "right",
...defaultSeriesOptions,
- lineWidth: 1,
+ // lineWidth: 1,
...options,
...options.options,
...(base ? { baseValue: { type: "price", price: base } } : {}),
diff --git a/app/src/scripts/lightweightCharts/series/creators/legend.ts b/app/src/scripts/lightweightCharts/series/creators/legend.ts
index 4a02aebc8..03be55b87 100644
--- a/app/src/scripts/lightweightCharts/series/creators/legend.ts
+++ b/app/src/scripts/lightweightCharts/series/creators/legend.ts
@@ -8,26 +8,26 @@ import {
} from "/src/scripts/utils/urlParams";
import { createRWS } from "/src/solid/rws";
-export function createSeriesLegend({
+export function createSeriesLegend({
id,
presetId,
title,
color,
- series,
+ seriesList,
defaultVisible = true,
disabled: _disabled,
visible: _visible,
- url,
+ dataset,
}: {
id: string;
presetId: string;
title: string;
color: Accessor;
- series: ISeriesApi;
+ seriesList: Accessor | undefined>[];
defaultVisible?: boolean;
disabled?: Accessor;
visible?: RWS;
- url?: string;
+ dataset: ResourceDataset;
}) {
const storageID = `${presetId}-${id}`;
@@ -43,12 +43,6 @@ export function createSeriesLegend({
const drawn = createMemo(() => visible() && !disabled());
- createEffect(() => {
- series.applyOptions({
- visible: drawn(),
- });
- });
-
createEffect(() => {
if (disabled()) {
return;
@@ -68,11 +62,11 @@ export function createSeriesLegend({
return {
id,
title,
- series,
+ seriesList,
color,
visible,
disabled,
drawn,
- url,
+ dataset,
};
}
diff --git a/app/src/scripts/presets/addresses/index.ts b/app/src/scripts/presets/addresses/index.ts
index f29b0027c..978c12c23 100644
--- a/app/src/scripts/presets/addresses/index.ts
+++ b/app/src/scripts/presets/addresses/index.ts
@@ -28,7 +28,6 @@ export function createPresets({
{
title: `Total Non Empty Address`,
color: colors.bitcoin,
- seriesType: SeriesType.Area,
dataset: params.datasets[scale].address_count,
},
],
@@ -67,7 +66,6 @@ export function createPresets({
{
title: `Total Addresses Created`,
color: colors.bitcoin,
- seriesType: SeriesType.Area,
dataset: params.datasets[scale].created_addresses,
},
],
@@ -87,7 +85,6 @@ export function createPresets({
{
title: `Total Empty Addresses`,
color: colors.darkWhite,
- seriesType: SeriesType.Area,
dataset: params.datasets[scale].empty_addresses,
},
],
@@ -143,25 +140,46 @@ function createAddressPresetFolder({
color,
datasetKey,
}),
- {
- name: `Split By Liquidity`,
- tree: liquidities.map(
- (liquidity): PartialPresetFolder => ({
- name: liquidity.name,
- tree: createCohortPresetList({
- title: `${liquidity.name} ${name}`,
- name: `${liquidity.name} ${name}`,
- scale,
- color,
- datasetKey: `${liquidity.key}_${datasetKey}`,
- }),
- }),
- ),
- },
+ createLiquidityFolder({
+ scale,
+ name,
+ datasetKey,
+ color,
+ }),
],
};
}
+export function createLiquidityFolder({
+ scale,
+ color,
+ name,
+ datasetKey,
+}: {
+ scale: Scale;
+ name: string;
+ datasetKey: AddressCohortKey | "";
+ color: string;
+}): PartialPresetFolder {
+ return {
+ name: `Split By Liquidity`,
+ tree: liquidities.map(
+ (liquidity): PartialPresetFolder => ({
+ name: liquidity.name,
+ tree: createCohortPresetList({
+ title: `${liquidity.name} ${name}`,
+ name: `${liquidity.name} ${name}`,
+ scale,
+ color,
+ datasetKey: !datasetKey
+ ? liquidity.key
+ : `${liquidity.key}_${datasetKey}`,
+ }),
+ }),
+ ),
+ };
+}
+
export function createAddressCountPreset({
scale,
color,
diff --git a/app/src/scripts/presets/apply.ts b/app/src/scripts/presets/apply.ts
index dbb9224a5..ed6b79bb4 100644
--- a/app/src/scripts/presets/apply.ts
+++ b/app/src/scripts/presets/apply.ts
@@ -1,10 +1,10 @@
import { createRWS } from "/src/solid/rws";
+import { chunkIdToIndex } from "../datasets/resource";
import { createChart } from "../lightweightCharts/chart/create";
-import { chartState } from "../lightweightCharts/chart/state";
+import { setMinMaxMarkers } from "../lightweightCharts/chart/markers";
import { initTimeScale } from "../lightweightCharts/chart/time";
import { setWhitespace } from "../lightweightCharts/chart/whitespace";
-import { createAreaSeries } from "../lightweightCharts/series/creators/area";
import {
createBaseLineSeries,
DEFAULT_BASELINE_COLORS,
@@ -16,13 +16,12 @@ 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,
+ Line,
Based,
- Area,
Histogram,
+ Candlestick,
}
type SeriesConfig =
@@ -33,6 +32,7 @@ type SeriesConfig =
seriesType: SeriesType.Based;
title: string;
options?: BaselineSeriesOptions;
+ priceScaleOptions?: DeepPartialPriceScaleOptions;
defaultVisible?: boolean;
}
| {
@@ -42,15 +42,27 @@ type SeriesConfig =
seriesType: SeriesType.Histogram;
title: string;
options?: DeepPartialHistogramOptions;
+ priceScaleOptions?: DeepPartialPriceScaleOptions;
defaultVisible?: boolean;
}
+ | {
+ dataset: ResourceDataset;
+ seriesType: SeriesType.Candlestick;
+ priceScaleOptions?: DeepPartialPriceScaleOptions;
+ colors?: undefined;
+ color?: undefined;
+ options?: DeepPartialLineOptions;
+ defaultVisible?: boolean;
+ title: string;
+ }
| {
dataset: ResourceDataset;
color: string;
colors?: undefined;
- seriesType?: SeriesType.Normal | SeriesType.Area;
+ seriesType?: SeriesType.Line;
title: string;
options?: DeepPartialLineOptions;
+ priceScaleOptions?: DeepPartialPriceScaleOptions;
defaultVisible?: boolean;
};
@@ -65,18 +77,28 @@ export function applySeriesList({
priceDataset,
priceOptions,
legendSetter,
+ dark,
+ activeRange,
}: {
charts: RWS;
parentDiv: HTMLDivElement;
preset: Preset;
- legendSetter: Setter;
+ legendSetter: Setter;
priceDataset?: ResourceDataset;
priceOptions?: PriceSeriesOptions;
priceScaleOptions?: DeepPartialPriceScaleOptions;
top?: SeriesConfig[];
bottom?: SeriesConfig[];
datasets: Datasets;
+ dark: boolean;
+ activeRange: RWS;
}) {
+ // ---
+ // Reset states
+ // ---
+
+ legendSetter([]);
+
reactiveChartList.set((charts) => {
charts.forEach((chart) => {
chart.remove();
@@ -85,29 +107,36 @@ export function applySeriesList({
return [];
});
+ activeRange.set([]);
+
parentDiv.replaceChildren();
+ // ---
+ // Done
+ // ---
+
const scale = preset.scale;
- const legendList: PresetLegend = [];
+ const presetLegend: SeriesLegend[] = [];
- const priceSeriesType = createRWS<"Candlestick" | "Line">("Candlestick");
+ const priceSeriesType = createRWS("Candlestick");
- // const valuesSkipped = createRWS(0);
+ const activeDatasets: ResourceDataset[] = [];
- const activeDatasets: Set> = new Set();
+ const lastActiveIndex = createMemo(() => {
+ const last = activeRange().at(-1);
+ return last !== undefined ? chunkIdToIndex(scale, last) : undefined;
+ });
+
+ const exactRange = createRWS(undefined as TimeRange | undefined);
const charts = [top || [], bottom]
.flatMap((list) => (list ? [list] : []))
- .flatMap((seriesConfigList, index, array) => {
+ .flatMap((seriesConfigList, index) => {
if (index !== 0 && seriesConfigList.length === 0) {
return [];
}
- const isAnyArea = seriesConfigList.find(
- (config) => config.seriesType === SeriesType.Area,
- );
-
const div = document.createElement("div");
div.className = "w-full cursor-crosshair min-h-0 border-orange-200/10";
@@ -115,28 +144,40 @@ export function applySeriesList({
parentDiv.appendChild(div);
const chart = createChart(scale, div, {
- ...priceScaleOptions,
- ...(isAnyArea
- ? {
- scaleMargins: {
- bottom: 0,
- },
- }
- : {}),
+ dark,
+ priceScaleOptions: {
+ ...priceScaleOptions,
+ },
});
- chartState.chart = chart;
-
if (!chart) {
console.log("chart: undefined");
return [];
}
- setWhitespace(chart, scale);
+ const whitespace = setWhitespace(chart, scale);
- const seriesList: ISeriesApi[] = [];
+ // return [
+ // {
+ // scale,
+ // div,
+ // chart,
+ // whitespace,
+ // legendList: [],
+ // debouncedSetMinMaxMarkers: () => {},
+ // },
+ // ];
- const _legendList: PresetLegend = [];
+ const chartLegend: SeriesLegend[] = [];
+
+ const debouncedSetMinMaxMarkers = debounce(() => {
+ setMinMaxMarkers({
+ scale,
+ visibleRange: exactRange(),
+ legendList: chartLegend,
+ activeRange,
+ });
+ }, 20);
if (index === 0) {
const dataset =
@@ -145,102 +186,93 @@ export function applySeriesList({
typeof priceDataset
>);
- activeDatasets.add(dataset);
+ activeDatasets.push(dataset);
- const price = applyPriceSeries({
- chart,
- preset,
- seriesType: priceSeriesType,
- // valuesSkipped,
- dataset,
- options: priceOptions,
+ const title = priceOptions?.title || "Price";
+
+ const priceScaleOptions: DeepPartialPriceScaleOptions = {
+ mode: 1,
+ ...priceOptions?.priceScaleOptions,
+ };
+
+ function createPriceSeries(seriesType: PriceSeriesType) {
+ let seriesConfig: SeriesConfig;
+
+ if (seriesType === "Candlestick") {
+ seriesConfig = {
+ dataset,
+ title,
+ seriesType: SeriesType.Candlestick,
+ options: priceOptions,
+ priceScaleOptions,
+ };
+ } else {
+ seriesConfig = {
+ dataset,
+ title,
+ color: colors.white,
+ options: priceOptions?.seriesOptions,
+ priceScaleOptions,
+ };
+ }
+
+ return createSeriesGroup({
+ activeRange,
+ seriesConfig,
+ chart,
+ chartLegend,
+ lastActiveIndex,
+ preset,
+ disabled: () => priceSeriesType() !== seriesType,
+ debouncedSetMinMaxMarkers,
+ });
+ }
+
+ const priceCandlestickLegend = createPriceSeries("Candlestick");
+ const priceLineLegend = createPriceSeries("Line");
+
+ createEffect(() => {
+ priceCandlestickLegend.visible.set(priceLineLegend.visible());
});
- _legendList.push(price.lineLegend, price.ohlcLegend);
-
- seriesList.push(price.lineLegend.series, price.ohlcLegend.series);
+ createEffect(() => {
+ priceLineLegend.visible.set(priceCandlestickLegend.visible());
+ });
}
- seriesList.push(
- ...seriesConfigList
- .reverse()
- .map(
- ({
- dataset,
- color,
- colors,
- seriesType: type,
- title,
- options,
- defaultVisible,
- }) => {
- activeDatasets.add(dataset);
+ seriesConfigList.reverse().forEach((seriesConfig) => {
+ activeDatasets.push(seriesConfig.dataset);
- let series: ISeriesApi<
- "Baseline" | "Line" | "Area" | "Histogram"
- >;
-
- if (type === SeriesType.Based) {
- series = createBaseLineSeries(chart, {
- color,
- ...options,
- });
- } else if (type === SeriesType.Area) {
- series = createAreaSeries(chart, {
- color,
- autoscaleInfoProvider: (
- getInfo: () => AutoscaleInfo | null,
- ) => {
- const info = getInfo();
- if (info) {
- info.priceRange.minValue = 0;
- }
- return info;
- },
- ...options,
- });
- } else if (type === SeriesType.Histogram) {
- series = createHistogramSeries(chart, {
- color,
- ...options,
- });
- } else {
- series = createLineSeries(chart, {
- color,
- ...options,
- });
- }
-
- _legendList.push(
- createSeriesLegend({
- id: stringToId(title),
- presetId: preset.id,
- title,
- series,
- color: () => colors || color || DEFAULT_BASELINE_COLORS,
- defaultVisible,
- url: dataset.url,
- }),
- );
-
- createEffect(() => {
- const values = dataset.values();
- console.log(values.length);
-
- series.setData(values);
- });
-
- return series;
- },
- ),
- );
-
- _legendList.forEach((legend) => {
- legendList.splice(0, 0, legend);
+ createSeriesGroup({
+ activeRange,
+ seriesConfig,
+ chartLegend,
+ chart,
+ preset,
+ lastActiveIndex,
+ debouncedSetMinMaxMarkers,
+ });
});
- return [{ div, chart, seriesList, legendList: _legendList }];
- });
+ chartLegend.forEach((legend) => {
+ presetLegend.splice(0, 0, legend);
+
+ createEffect(on(legend.visible, debouncedSetMinMaxMarkers));
+ });
+
+ createEffect(on(exactRange, debouncedSetMinMaxMarkers));
+
+ return [
+ {
+ scale,
+ div,
+ chart,
+ whitespace,
+ legendList: chartLegend,
+ debouncedSetMinMaxMarkers,
+ },
+ ];
+ }) satisfies ChartObject[];
createEffect(() => {
const visibleCharts: typeof charts = [];
@@ -267,344 +299,268 @@ export function applySeriesList({
});
});
- // const seriesType = createRWS(
- // checkIfUpClose(chart, chartState.range) || "Candlestick",
- // );
-
- function updateVisibleRangeRatio(
- chart: IChartApi,
- range: LogicalRange | null,
- ) {
- if (!range) return;
-
- try {
- const width = chart.timeScale().width();
-
- 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 / 1.25));
- }
- } catch {}
- }
-
- const debouncedUpdateVisibleRangeRatio = debounce(
- updateVisibleRangeRatio,
+ const debouncedUpdateVisiblePriceSeriesType = debounce(
+ updateVisiblePriceSeriesType,
50,
);
initTimeScale({
- activeDatasets,
+ scale,
+ charts,
+ activeRange,
+ exactRange,
});
- charts.forEach(({ chart }, index) => {
+ const activeDatasetsLength = activeDatasets.length;
+ createEffect(() => {
+ const range = activeRange();
+
+ 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((timeRange) => {
- // Last chart otherwise length of timescale is Infinity
- if (index === charts.length - 1) {
- debouncedUpdateVisibleRangeRatio(chart, timeRange);
+ if (!timeRange) return;
+
+ // Must be the chart with the visible timeScale
+ if (i === lastChartIndex) {
+ debouncedUpdateVisiblePriceSeriesType(
+ chart,
+ timeRange,
+ priceSeriesType,
+ );
}
- charts.forEach(({ chart: _chart }, _index) => {
- if (timeRange && index !== _index) {
- _chart.timeScale().setVisibleLogicalRange(timeRange);
+ for (let j = 0; j <= lastChartIndex; j++) {
+ if (i !== j) {
+ charts[j].chart.timeScale().setVisibleLogicalRange(timeRange);
}
- });
+ }
});
chart.subscribeCrosshairMove(({ time, sourceEvent }) => {
// Don't override crosshair position from scroll event
if (time && !sourceEvent) return;
- charts.forEach(({ chart: _chart, seriesList }, _index) => {
- const first = seriesList.at(0);
+ for (let j = 0; j <= lastChartIndex; j++) {
+ const whitespace = charts[j].whitespace;
+ const otherChart = charts[j].chart;
- if (first && index !== _index) {
+ if (whitespace && i !== j) {
if (time) {
- _chart.setCrosshairPosition(NaN, time, first);
+ otherChart.setCrosshairPosition(NaN, time, whitespace);
} else {
// No time when mouse goes outside the chart
- _chart.clearCrosshairPosition();
+ otherChart.clearCrosshairPosition();
}
}
- });
+ }
});
- });
+ }
- legendSetter(legendList);
+ legendSetter(presetLegend);
reactiveChartList.set(() => charts.map(({ chart }) => chart));
}
-function applyPriceSeries<
- Scale extends ResourceScale,
- T extends OHLC | number,
->({
- chart,
- preset,
- dataset,
- seriesType,
- // valuesSkipped,
- options,
-}: {
- chart: IChartApi;
- preset: Preset;
- // valuesSkipped: Accessor;
- seriesType: Accessor<"Candlestick" | "Line">;
- dataset: ResourceDataset;
- options?: PriceSeriesOptions;
-}) {
- // console.time("series add");
+function updateVisiblePriceSeriesType(
+ chart: IChartApi,
+ range: LogicalRange,
+ priceSeriesType: RWS,
+) {
+ try {
+ const width = chart.timeScale().width();
- const id = options?.id || "price";
- const title = options?.title || "Price";
+ const ratio = (range.to - range.from) / width;
- const url = "url" in dataset ? (dataset as any).url : undefined;
-
- const priceScaleOptions: DeepPartialPriceScaleOptions = {
- mode: 1,
- ...options?.priceScaleOptions,
- };
-
- let [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;
-
- 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);
- }
+ if (ratio <= 0.5) {
+ priceSeriesType.set("Candlestick");
+ } else {
+ priceSeriesType.set("Line");
}
- });
-
- return { ohlcLegend, lineLegend };
+ } catch {}
}
-// // const computeDrawnSeriesValues = debounce(_computeDrawnSeriesValues, 100);
+function createSeriesGroup({
+ activeRange,
+ seriesConfig,
+ preset,
+ chartLegend,
+ chart,
+ disabled,
+ lastActiveIndex,
+ debouncedSetMinMaxMarkers,
+}: {
+ activeRange: Accessor;
+ seriesConfig: SeriesConfig;
+ preset: Preset;
+ chart: IChartApi;
+ chartLegend: SeriesLegend[];
+ lastActiveIndex: Accessor;
+ disabled?: Accessor;
+ debouncedSetMinMaxMarkers: VoidFunction;
+}) {
+ const {
+ dataset,
+ title,
+ colors,
+ color,
+ defaultVisible,
+ seriesType: type,
+ options,
+ priceScaleOptions,
+ } = seriesConfig;
-// function computeDrawnSeriesValues<
-// S extends ResourceScale,
-// T extends OHLC | number,
-// >(
-// dataset: ResourceDataset,
-// valuesSkipped: number,
-// callback: (chunks: any, v: number, l: number) => void,
-// // seriesList: ISeriesApi[],
-// ) {
-// // console.time(dataset.url);
+ const scale = preset.scale;
-// const { fetchedJSONs, activeRange: _activeRange } = dataset;
+ const seriesList: RWS<
+ ISeriesApi<"Baseline" | "Line" | "Histogram" | "Candlestick"> | undefined
+ >[] = new Array(dataset.fetchedJSONs.length);
-// const activeRange = _activeRange();
+ let defaultSeriesColor: string | string[] | undefined = undefined;
-// const valuesSkippedPlus1 = valuesSkipped + 1;
+ const legend = createSeriesLegend({
+ id: stringToId(title),
+ presetId: preset.id,
+ title,
+ seriesList,
+ color: () =>
+ colors || color || defaultSeriesColor || DEFAULT_BASELINE_COLORS,
+ defaultVisible,
+ disabled,
+ dataset,
+ });
-// if (valuesSkippedPlus1 === 1) {
-// console.log("todo valuesSkippedPlus1===1, skip for now");
-// }
+ chartLegend.push(legend);
-// // for (let i = 0; i < seriesList.length; i++) {
-// // seriesList[i].
-// // }
+ dataset.fetchedJSONs.forEach((json, index) => {
+ const series: (typeof seriesList)[number] = createRWS(undefined);
-// const chunks = new Array(activeRange.length);
-// let length = 0;
+ seriesList[index] = series;
-// for (let i = 0; i < chunks.length; i++) {
-// const chunk =
-// fetchedJSONs[chunkIdToIndex(dataset.scale, activeRange[i])]?.vec?.() ||
-// [];
+ createEffect(() => {
+ const values = json.vec();
+ if (!values) return;
-// chunks[i] = chunk;
+ untrack(() => {
+ let s = series();
-// length += Math.ceil(chunk.length / valuesSkippedPlus1);
-// }
+ if (!s) {
+ switch (type) {
+ case SeriesType.Based: {
+ s = createBaseLineSeries(chart, {
+ color,
+ ...options,
+ });
-// callback(chunks, valuesSkippedPlus1, length);
+ break;
+ }
+ case SeriesType.Candlestick: {
+ const candlestickSeries = createCandlesticksSeries(
+ chart,
+ options,
+ );
-// // setValues(chunks, valuesSkippedPlus1, length, callback);
-// // }
+ s = candlestickSeries[0];
+ defaultSeriesColor = candlestickSeries[1];
-// // // const debouncedSetValues = debounce(setValues, 50);
-// // function setValues(
-// // chunks: any[],
-// // valuesSkippedPlus1: number,
-// // length: number,
-// // callback: (values: any[]) => void,
-// // ) {
-// // const values = new Array(length);
+ break;
+ }
+ case SeriesType.Histogram: {
+ s = createHistogramSeries(chart, {
+ color,
+ ...options,
+ });
-// // let i = 0;
-// // for (let k = 0; k < activeRange.length; k++) {
-// // const chunk = chunks[k];
-// // // const chunk =
-// // // fetchedJSONs[chunkIdToIndex(dataset.scale, activeRange[k])]?.vec?.() ||
-// // // [];
+ break;
+ }
+ default:
+ case SeriesType.Line: {
+ s = createLineSeries(chart, {
+ color,
+ ...options,
+ });
-// // 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]);
-// // }
+ break;
+ }
+ }
-// // // i++;
-// // }
-// // }
+ if (priceScaleOptions) {
+ s.priceScale().applyOptions(priceScaleOptions);
+ }
-// // console.log(i);
+ series.set(s);
+ }
-// // if (i !== values.length) {
-// // console.log({ n: i, values });
-// // throw Error("error");
-// // }
+ s.setData(values);
-// // console.timeEnd(dataset.url);
-// }
+ untrack(() => {
+ 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 = activeRange();
+
+ 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 016961ea3..81beb3bbc 100644
--- a/app/src/scripts/presets/blocks/index.ts
+++ b/app/src/scripts/presets/blocks/index.ts
@@ -162,7 +162,6 @@ export function createPresets() {
{
title: "Mined",
color: colors.bitcoin,
- seriesType: SeriesType.Area,
dataset: params.datasets.date.total_blocks_mined,
},
],
@@ -184,7 +183,6 @@ export function createPresets() {
{
title: "Size (MB)",
color: colors.darkWhite,
- seriesType: SeriesType.Area,
dataset: params.datasets.date.cumulative_block_size,
},
],
diff --git a/app/src/scripts/presets/index.ts b/app/src/scripts/presets/index.ts
index b6c7d3d6a..f6cca5b7e 100644
--- a/app/src/scripts/presets/index.ts
+++ b/app/src/scripts/presets/index.ts
@@ -4,7 +4,10 @@ import { colors } from "../utils/colors";
import { replaceHistory } from "../utils/history";
import { stringToId } from "../utils/id";
import { resetURLParams } from "../utils/urlParams";
-import { createPresets as createAddressesPresets } from "./addresses";
+import {
+ createPresets as createAddressesPresets,
+ createLiquidityFolder,
+} from "./addresses";
import { createPresets as createBlocksPresets } from "./blocks";
import { createPresets as createCoinblocksPresets } from "./coinblocks";
import { createPresets as createHodlersPresets } from "./hodlers";
@@ -31,7 +34,7 @@ export function createPresets(): Presets {
{
name: "By Date",
tree: [
- createMarketPresets({ scale: "date" }),
+ createMarketPresets("date"),
createBlocksPresets(),
createMinersPresets("date"),
createTransactionsPresets("date"),
@@ -42,6 +45,12 @@ export function createPresets(): Presets {
name: "",
title: "",
}),
+ createLiquidityFolder({
+ scale: "date",
+ color: colors.bitcoin,
+ datasetKey: "",
+ name: "",
+ }),
createHodlersPresets({ scale: "date" }),
createAddressesPresets({ scale: "date" }),
createCoinblocksPresets({ scale: "date" }),
@@ -50,7 +59,7 @@ export function createPresets(): Presets {
{
name: "By Height",
tree: [
- createMarketPresets({ scale: "height" }),
+ createMarketPresets("height"),
createMinersPresets("height"),
createTransactionsPresets("height"),
...createCohortPresetList({
@@ -60,6 +69,12 @@ export function createPresets(): Presets {
datasetKey: "",
title: "",
}),
+ createLiquidityFolder({
+ scale: "height",
+ color: colors.bitcoin,
+ datasetKey: "",
+ name: "",
+ }),
createHodlersPresets({ scale: "height" }),
createAddressesPresets({ scale: "height" }),
createCoinblocksPresets({ scale: "height" }),
@@ -101,7 +116,7 @@ export function createPresets(): Presets {
const serializedHistory: SerializedPresetsHistory = history().map(
({ preset, date }) => ({
p: preset.id,
- d: date.valueOf(),
+ d: date.getTime(),
}),
);
diff --git a/app/src/scripts/presets/market/index.ts b/app/src/scripts/presets/market/index.ts
index a6738ebea..5ebc350ea 100644
--- a/app/src/scripts/presets/market/index.ts
+++ b/app/src/scripts/presets/market/index.ts
@@ -4,7 +4,7 @@ import { createPresets as createAveragesPresets } from "./averages";
import { createPresets as createIndicatorsPresets } from "./indicators";
import { createPresets as createReturnsPresets } from "./returns";
-export function createPresets({ scale }: { scale: ResourceScale }) {
+export function createPresets(scale: ResourceScale) {
return {
name: "Market",
tree: [
@@ -18,25 +18,6 @@ export function createPresets({ scale }: { scale: ResourceScale }) {
},
description: "",
},
- {
- scale,
- icon: IconTablerPercentage,
- name: "Performance",
- title: "Market Performance",
- applyPreset(params) {
- return applySeriesList({
- ...params,
- priceOptions: {
- id: "performance",
- title: "Performance",
- priceScaleOptions: {
- mode: 2,
- },
- },
- });
- },
- description: "",
- },
{
scale,
icon: IconTablerInfinity,
diff --git a/app/src/scripts/presets/templates/cohort.ts b/app/src/scripts/presets/templates/cohort.ts
index 3cf43a640..33b25ba21 100644
--- a/app/src/scripts/presets/templates/cohort.ts
+++ b/app/src/scripts/presets/templates/cohort.ts
@@ -60,7 +60,6 @@ export function createCohortPresetList({
{
title: "Count",
color,
- seriesType: SeriesType.Area,
dataset: params.datasets[scale][`${datasetPrefix}utxo_count`],
},
],
@@ -105,7 +104,6 @@ export function createCohortPresetList({
{
title: `${name} Realized Cap.`,
color,
- seriesType: SeriesType.Area,
dataset:
params.datasets[scale][`${datasetPrefix}realized_cap`],
},
@@ -160,7 +158,6 @@ export function createCohortPresetList({
dataset:
params.datasets[scale][`${datasetPrefix}realized_profit`],
color: colors.profit,
- seriesType: SeriesType.Area,
},
],
});
@@ -181,7 +178,6 @@ export function createCohortPresetList({
dataset:
params.datasets[scale][`${datasetPrefix}realized_loss`],
color: colors.loss,
- seriesType: SeriesType.Area,
},
],
});
@@ -274,7 +270,6 @@ export function createCohortPresetList({
{
title: "Cumulative Realized Profit",
color: colors.profit,
- seriesType: SeriesType.Area,
dataset:
params.datasets[scale][
`${datasetPrefix}cumulative_realized_profit`
@@ -297,7 +292,6 @@ export function createCohortPresetList({
{
title: "Cumulative Realized Loss",
color: colors.loss,
- seriesType: SeriesType.Area,
dataset:
params.datasets[scale][
`${datasetPrefix}cumulative_realized_loss`
@@ -371,7 +365,6 @@ export function createCohortPresetList({
dataset:
params.datasets[scale][`${datasetPrefix}unrealized_profit`],
color: colors.profit,
- seriesType: SeriesType.Area,
},
],
});
@@ -393,7 +386,6 @@ export function createCohortPresetList({
dataset:
params.datasets[scale][`${datasetPrefix}unrealized_loss`],
color: colors.loss,
- seriesType: SeriesType.Area,
},
],
});
@@ -539,7 +531,6 @@ export function createCohortPresetList({
{
title: "Supply",
color,
- seriesType: SeriesType.Area,
dataset: params.datasets[scale][`${datasetPrefix}supply`],
},
],
@@ -558,7 +549,6 @@ export function createCohortPresetList({
{
title: "Supply",
color: colors.profit,
- seriesType: SeriesType.Area,
dataset:
params.datasets[scale][
`${datasetPrefix}supply_in_profit`
@@ -581,7 +571,6 @@ export function createCohortPresetList({
{
title: "Supply",
color: colors.loss,
- seriesType: SeriesType.Area,
dataset:
params.datasets[scale][
`${datasetPrefix}supply_in_loss`
@@ -658,7 +647,6 @@ export function createCohortPresetList({
{
title: "Supply",
color,
- seriesType: SeriesType.Area,
dataset:
params.datasets[scale][
`${datasetPrefix}supply_to_circulating_supply_ratio`
@@ -681,7 +669,6 @@ export function createCohortPresetList({
{
title: "Supply",
color: colors.profit,
- seriesType: SeriesType.Area,
dataset:
params.datasets[scale][
`${datasetPrefix}supply_in_profit_to_circulating_supply_ratio`
@@ -703,7 +690,6 @@ export function createCohortPresetList({
bottom: [
{
title: "Supply",
- seriesType: SeriesType.Area,
color: colors.loss,
dataset:
params.datasets[scale][
@@ -779,7 +765,6 @@ export function createCohortPresetList({
{
title: "Supply",
color: colors.profit,
- seriesType: SeriesType.Area,
dataset:
params.datasets[scale][
`${datasetPrefix}supply_in_profit_to_own_supply_ratio`
@@ -801,7 +786,6 @@ export function createCohortPresetList({
bottom: [
{
title: "Supply",
- seriesType: SeriesType.Area,
color: colors.loss,
dataset:
params.datasets[scale][
diff --git a/app/src/scripts/presets/types.d.ts b/app/src/scripts/presets/types.d.ts
index 117362bd5..d38124ce7 100644
--- a/app/src/scripts/presets/types.d.ts
+++ b/app/src/scripts/presets/types.d.ts
@@ -24,7 +24,9 @@ type ApplyPreset = (params: {
parentDiv: HTMLDivElement;
datasets: Datasets;
preset: Preset;
- legendSetter: Setter;
+ legendSetter: Setter;
+ dark: boolean;
+ activeRange: RWS;
}) => void;
interface PartialPresetFolder {
@@ -58,4 +60,13 @@ interface Presets {
select(preset: Preset): void;
}
-type PresetLegend = SeriesLegend[];
+type PriceSeriesType = "Candlestick" | "Line";
+
+interface ChartObject {
+ scale: ResourceScale;
+ div: HTMLDivElement;
+ chart: IChartApi;
+ whitespace: ISeriesApi<"Line">;
+ legendList: SeriesLegend[];
+ debouncedSetMinMaxMarkers: VoidFunction;
+}
diff --git a/app/src/scripts/utils/date.ts b/app/src/scripts/utils/date.ts
index be65c26a9..38a48cb12 100644
--- a/app/src/scripts/utils/date.ts
+++ b/app/src/scripts/utils/date.ts
@@ -7,4 +7,4 @@ export const dateToString = (date: Date) => date.toJSON().split("T")[0];
// export const FIVE_MONTHS_IN_DAYS = 30 * 5;
export const getNumberOfDaysBetweenTwoDates = (oldest: Date, youngest: Date) =>
- Math.round(Math.abs((youngest.valueOf() - oldest.valueOf()) / ONE_DAY_IN_MS));
+ Math.round(Math.abs((youngest.getTime() - oldest.getTime()) / ONE_DAY_IN_MS));
diff --git a/app/src/scripts/ws/kraken.ts b/app/src/scripts/ws/kraken.ts
index ca4118a91..4e040ab3f 100644
--- a/app/src/scripts/ws/kraken.ts
+++ b/app/src/scripts/ws/kraken.ts
@@ -31,7 +31,6 @@ export const krakenAPI = {
const candle: DatasetCandlestickData = {
// date: dateStr,
- number: new Date(dateStr).valueOf() / ONE_DAY_IN_MS,
time: dateStr,
open: Number(open),
high: Number(high),
diff --git a/app/src/types/lightweight-charts.d.ts b/app/src/types/lightweight-charts.d.ts
index 829aa3de6..3fd528865 100644
--- a/app/src/types/lightweight-charts.d.ts
+++ b/app/src/types/lightweight-charts.d.ts
@@ -54,6 +54,10 @@ type DeepPartialLineOptions = DeepPartial<
LineStyleOptions & SeriesOptionsCommon
>;
+type DeepPartialCandlestickOptions = DeepPartial<
+ CandlestickStyleOptions & SeriesOptionsCommon
+>;
+
type DeepPartialHistogramOptions = DeepPartial<
HistogramStyleOptions & SeriesOptionsCommon
>;
diff --git a/app/src/types/self.d.ts b/app/src/types/self.d.ts
index 09a953e10..977965ef6 100644
--- a/app/src/types/self.d.ts
+++ b/app/src/types/self.d.ts
@@ -6,10 +6,6 @@ interface Heighted {
height: number;
}
-interface Numbered {
- number: number;
-}
-
interface Valued {
value: number;
}
diff --git a/parser/Cargo.lock b/parser/Cargo.lock
index 6547ae56b..843ab48cb 100644
--- a/parser/Cargo.lock
+++ b/parser/Cargo.lock
@@ -1272,7 +1272,7 @@ dependencies = [
[[package]]
name = "parser"
-version = "0.1.1"
+version = "0.2.0"
dependencies = [
"allocative",
"bincode",
diff --git a/parser/Cargo.toml b/parser/Cargo.toml
index a64ebe9e1..9c6070831 100644
--- a/parser/Cargo.toml
+++ b/parser/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "parser"
-version = "0.1.1"
+version = "0.2.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
diff --git a/parser/README.md b/parser/README.md
index 73b723354..8a8463391 100644
--- a/parser/README.md
+++ b/parser/README.md
@@ -25,3 +25,4 @@ The backbone of the project, it does most of the work by parsing and then comput
- Avoid floats as much as possible
- Use structs like `WAmount` and `Price` for calculations
- **Only** use `WAmount.to_btc()` when inserting or computing inside a dataset. It is **very** expensive.
+- No `Arc`, `Rc`, `Mutex` even from third party libraries, they're slower
diff --git a/server/Cargo.lock b/server/Cargo.lock
index fc01ca8fc..7c788018f 100644
--- a/server/Cargo.lock
+++ b/server/Cargo.lock
@@ -1463,7 +1463,7 @@ dependencies = [
[[package]]
name = "parser"
-version = "0.1.1"
+version = "0.2.0"
dependencies = [
"allocative",
"bincode",
@@ -1907,7 +1907,7 @@ dependencies = [
[[package]]
name = "server"
-version = "0.1.1"
+version = "0.2.0"
dependencies = [
"axum",
"bincode",
diff --git a/server/Cargo.toml b/server/Cargo.toml
index 82776dd0c..ab4264eb7 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "server"
-version = "0.1.1"
+version = "0.2.0"
edition = "2021"
[dependencies]
diff --git a/server/run.sh b/server/run.sh
index 2b79330a8..d992d4401 100755
--- a/server/run.sh
+++ b/server/run.sh
@@ -1,2 +1,16 @@
+# Restart cloudflared
+# Sometimes it's acting up
+if command -v cloudflared &> /dev/null; then
+ # For Mac OS users
+ if [ "$(uname)" == "Darwin" ]; then
+ echo "Restarting cloudflared..."
+
+ sudo launchctl stop com.cloudflare.cloudflared
+ sudo launchctl unload /Library/LaunchDaemons/com.cloudflare.cloudflared.plist
+ sudo launchctl load /Library/LaunchDaemons/com.cloudflare.cloudflared.plist
+ sudo launchctl start com.cloudflare.cloudflared
+ fi
+fi
+
cargo watch -w "./src" -w "./run.sh" -x "run -r"
# cargo watch -w "./src" -w "./run.sh" -w "../datasets/disk_path_to_type.json" -x "run -r"