app: charts: add unit and price mode switch

This commit is contained in:
k
2024-07-24 00:05:18 +02:00
parent bf169d6954
commit 8f5f28ede6
34 changed files with 1153 additions and 987 deletions
+2
View File
@@ -48,6 +48,8 @@
- General
- Added chart scroll button for nice animations à la Wicked
- Added scale mode switch (Linear/Logarithmic) at the bottom right of all charts
- Added unit at the top left of all charts
- Added a backup API in case the main one fails or is offline
- Complete redesign of the datasets object
- Removed import of routes in JSON in favor for hardcoded typed routes in string format which resulted in:
+1 -1
View File
@@ -35,7 +35,7 @@
"pwa-asset-generator": "^6.3.1",
"rollup-plugin-visualizer": "^5.12.0",
"tailwindcss": "^3.4.6",
"typescript": "^5.5.3",
"typescript": "^5.5.4",
"unplugin-auto-import": "^0.18.0",
"unplugin-icons": "^0.19.0",
"vite": "^5.3.4",
+7 -7
View File
@@ -59,8 +59,8 @@ devDependencies:
specifier: ^3.4.6
version: 3.4.6
typescript:
specifier: ^5.5.3
version: 5.5.3
specifier: ^5.5.4
version: 5.5.4
unplugin-auto-import:
specifier: ^0.18.0
version: 0.18.0(rollup@2.79.1)
@@ -2635,7 +2635,7 @@ packages:
hasBin: true
dependencies:
caniuse-lite: 1.0.30001643
electron-to-chromium: 1.4.832
electron-to-chromium: 1.5.0
node-releases: 2.0.18
update-browserslist-db: 1.1.0(browserslist@4.23.2)
dev: true
@@ -3101,8 +3101,8 @@ packages:
jake: 10.9.2
dev: true
/electron-to-chromium@1.4.832:
resolution: {integrity: sha512-cTen3SB0H2SGU7x467NRe1eVcQgcuS6jckKfWJHia2eo0cHIGOqHoAxevIYZD4eRHcWjkvFzo93bi3vJ9W+1lA==}
/electron-to-chromium@1.5.0:
resolution: {integrity: sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA==}
dev: true
/emoji-regex@8.0.0:
@@ -5651,8 +5651,8 @@ packages:
possible-typed-array-names: 1.0.0
dev: true
/typescript@5.5.3:
resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==}
/typescript@5.5.4:
resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==}
engines: {node: '>=14.17'}
hasBin: true
dev: true
@@ -1,82 +1,369 @@
import { requestIdleCallbackPossible } from "/src/env";
import { applySeriesList } from "/src/scripts/presets/apply";
import { chunkIdToIndex } from "/src/scripts/datasets/resource";
import { createChart } from "/src/scripts/lightweightCharts/create";
import { createSeriesGroup } from "/src/scripts/lightweightCharts/group";
import { setMinMaxMarkers } from "/src/scripts/lightweightCharts/markers";
import {
debouncedUpdateVisiblePriceSeriesType,
updateVisiblePriceSeriesType,
} from "/src/scripts/lightweightCharts/price";
import {
initTimeScale,
setInitialTimeRange,
} from "/src/scripts/lightweightCharts/time";
import { setWhitespace } from "/src/scripts/lightweightCharts/whitespace";
import { SeriesType } from "/src/scripts/presets/enums";
import { colors } from "/src/scripts/utils/colors";
import { debounce } from "/src/scripts/utils/debounce";
import { createSL } from "/src/scripts/utils/selectableList/static";
import { webSockets } from "/src/scripts/ws";
import { classPropToString } from "/src/solid/classes";
import { createRWS } from "/src/solid/rws";
import { RadioGroup } from "../../settings";
export function Chart({
charts,
parentDiv,
presets,
datasets,
legendSetter,
dark,
activeDatasets,
activeIds,
charts,
chartsDrawn,
dark,
datasets,
exactRange,
firstChartSetter,
index,
lastActiveIndex,
lastChartIndex,
legendSetter,
preset: presetAccessor,
priceSeriesType,
seriesConfigs,
seriesCount,
}: {
charts: RWS<IChartApi[]>;
parentDiv: RWS<HTMLDivElement | undefined>;
presets: Presets;
datasets: Datasets;
legendSetter: Setter<SeriesLegend[]>;
dark: Accessor<boolean>;
activeDatasets: ReadWriteSignal<ResourceDataset<any, any>[]>;
activeIds: RWS<number[]>;
charts: ReadWriteSignal<
{
chart: RWS<IChartApi | undefined>;
whitespace: RWS<ISeriesApiAny | undefined>;
}[]
>;
chartsDrawn: Accessor<ReadWriteSignal<boolean>[]>;
dark: Accessor<boolean>;
datasets: Datasets;
exactRange: ReadWriteSignal<TimeRange>;
firstChartSetter: Setter<IChartApi | undefined>;
index: Accessor<number>;
lastActiveIndex: Accessor<number | undefined>;
lastChartIndex: Accessor<number>;
legendSetter: Setter<SeriesLegend[]>;
preset: Accessor<Preset>;
priceSeriesType: ReadWriteSignal<PriceSeriesType>;
seriesConfigs: SeriesConfig[];
seriesCount: Accessor<number>;
}) {
const wasIdle = createRWS(false);
const div = createRWS<HTMLDivElement | undefined>(undefined);
const chartIndex = index();
if (requestIdleCallbackPossible) {
const idleCallback = requestIdleCallback(() => {
wasIdle.set(true);
});
const isDrawn = chartsDrawn()[chartIndex];
const isLastDrawn = createMemo(
() => chartsDrawn().findLastIndex((drawn) => drawn()) === chartIndex,
);
onCleanup(() => {
cancelIdleCallback(idleCallback);
});
} else {
const timeout = setTimeout(() => {
wasIdle.set(true);
}, 500);
onCleanup(() => {
clearTimeout(timeout);
});
}
onMount(() => {
createEffect(() => {
const preset = presets.selected();
const div = parentDiv();
if (!wasIdle() || !div) return;
untrack(() => {
try {
console.log(`preset: ${preset.id}`);
applySeriesList({
charts,
parentDiv: div,
datasets,
preset,
legendSetter,
dark,
activeIds,
priceScaleOptions: preset.priceScaleOptions,
top: preset.top,
bottom: preset.bottom,
});
} catch (error) {
console.error("chart: render: failed", error);
}
});
});
onCleanup(() =>
charts.set((charts) => {
charts.forEach((chart) => {
chart.remove();
});
return [];
}),
);
const chartPriceModeKey = `chart-price-mode-${chartIndex}` as const;
const chartPriceMode = createSL(["Linear", "Log"] as const, {
saveable: {
key: chartPriceModeKey,
mode: "localStorage",
},
defaultValue: chartIndex === 0 ? "Log" : "Linear",
});
return <></>;
createEffect(
on([div, () => charts()[chartIndex]], ([div, chartConfig]) => {
console.log({
div,
chartConfig,
});
if (!div || !chartConfig) return;
const preset = presetAccessor();
const scale = preset.scale;
const chart = createChart({
scale,
element: div,
dark,
});
if (!chart) {
console.log("chart: undefined");
return;
}
const whitespace = setWhitespace(chart, scale);
batch(() => {
chartConfig.chart.set(chart);
chartConfig.whitespace.set(whitespace);
if (chartIndex === 0) {
firstChartSetter(chart);
}
});
const range = exactRange();
setInitialTimeRange({ chart, range });
if (chartIndex === 0) {
initTimeScale({
scale,
chart,
activeIds,
exactRange,
});
if (range) {
updateVisiblePriceSeriesType({
scale,
chart,
priceSeriesType,
timeRange: range,
});
}
}
const chartLegend: SeriesLegend[] = [];
onCleanup(() => {
chartLegend.length = 0;
});
const markerCallback = () =>
setMinMaxMarkers({
scale,
visibleRange: exactRange(),
legendList: chartLegend,
dark,
activeIds: activeIds,
});
const debouncedSetMinMaxMarkers = requestIdleCallbackPossible
? () => requestIdleCallback(markerCallback)
: debounce(
markerCallback,
seriesCount() * 10 + scale === "date" ? 50 : 100,
);
createEffect(on([exactRange, dark], debouncedSetMinMaxMarkers));
if (chartIndex === 0) {
const datasetPath: AnyDatasetPath = `/${scale}-to-price`;
const dataset = datasets.getOrImport(scale, datasetPath);
// Don't trigger reactivity by design
activeDatasets().push(dataset);
const title = "Price";
function createPriceSeries(seriesType: PriceSeriesType) {
let seriesConfig: SeriesConfig;
if (seriesType === "Candlestick") {
seriesConfig = {
datasetPath,
title,
seriesType: SeriesType.Candlestick,
};
} else {
seriesConfig = {
datasetPath,
title,
color: colors.white,
};
}
const priceSeries = createSeriesGroup({
scale,
datasets,
index: -1,
activeIds,
seriesConfig,
chart,
chartLegend,
lastActiveIndex,
preset,
disabled: () => priceSeriesType() !== seriesType,
debouncedSetMinMaxMarkers,
dark,
});
createEffect(() => {
const latest = webSockets.liveKrakenCandle.latest();
if (!latest) return;
const index = chunkIdToIndex(scale, latest.year);
const series = priceSeries.seriesList.at(index)?.();
series?.update(latest);
});
return priceSeries;
}
const priceCandlestickLegend = createPriceSeries("Candlestick");
const priceLineLegend = createPriceSeries("Line");
createEffect(() => {
priceCandlestickLegend.visible.set(priceLineLegend.visible());
});
createEffect(() => {
priceLineLegend.visible.set(priceCandlestickLegend.visible());
});
}
[...seriesConfigs].reverse().forEach((seriesConfig, index) => {
const dataset = datasets.getOrImport(scale, seriesConfig.datasetPath);
// Don't trigger reactivity by design
activeDatasets().push(dataset);
createSeriesGroup({
scale,
datasets,
activeIds,
index,
seriesConfig,
chartLegend,
chart,
preset,
lastActiveIndex,
debouncedSetMinMaxMarkers,
dark,
});
});
chartLegend.forEach((legend) => {
createEffect(on(legend.visible, debouncedSetMinMaxMarkers));
});
legendSetter((l) => {
for (let i = 0; i < chartLegend.length; i++) {
l.splice(0, 0, chartLegend[i]);
}
return l;
});
createEffect(() =>
isDrawn.set(() => chartLegend.some((legend) => legend.drawn())),
);
createEffect(() =>
chart.timeScale().applyOptions({
visible: isLastDrawn(),
}),
);
createEffect(() =>
chart.priceScale("right").applyOptions({
mode: chartPriceMode.selected() === "Linear" ? 0 : 1,
}),
);
chart.timeScale().subscribeVisibleLogicalRangeChange((logicalRange) => {
if (!logicalRange) return;
// Must be the chart with the visible timeScale
if (chartIndex === lastChartIndex()) {
debouncedUpdateVisiblePriceSeriesType({
scale,
chart,
logicalRange,
priceSeriesType,
});
}
for (
let otherChartIndex = 0;
otherChartIndex <= lastChartIndex();
otherChartIndex++
) {
if (chartIndex !== otherChartIndex) {
const chart = charts()[otherChartIndex].chart();
chart?.timeScale().setVisibleLogicalRange(logicalRange);
}
}
});
chart.subscribeCrosshairMove(({ time, sourceEvent }) => {
// Don't override crosshair position from scroll event
if (time && !sourceEvent) return;
for (
let otherChartIndex = 0;
otherChartIndex <= lastChartIndex();
otherChartIndex++
) {
const { whitespace: _whitespace, chart: _otherChart } =
charts()[otherChartIndex];
const otherChart = _otherChart();
const whitespace = _whitespace();
if (otherChart && whitespace && chartIndex !== otherChartIndex) {
if (time) {
otherChart.setCrosshairPosition(NaN, time, whitespace);
} else {
// No time when mouse goes outside the chart
otherChart.clearCrosshairPosition();
}
}
}
});
// Trigger reactivity now
activeDatasets.set((l) => l);
}),
);
return (
<div
style={{
height: isLastDrawn() ? "100%" : "calc(100% - 62px)",
"margin-bottom": isLastDrawn() ? "" : "-2px",
}}
class={classPropToString([
isDrawn()
? ["max-h-full", !isLastDrawn() ? "border-b" : "mb-[-2px]"]
: "max-h-0",
"border-lighter relative h-full min-h-0 w-full cursor-crosshair",
])}
>
<div ref={div.set} class="size-full" />
<Show when={isDrawn()}>
<div class="text-low-contrast absolute left-0 top-0 px-2 py-1.5 text-xs">
{chartIndex === 0
? ("US Dollars" satisfies Unit)
: presetAccessor().unit}
</div>
<div
style={{
bottom: `${isLastDrawn() ? 32 : 0}px`,
right: `77px`,
}}
class="text-low-contrast absolute z-50 px-3 py-0.5"
>
<RadioGroup size="xs" title={chartPriceModeKey} sl={chartPriceMode} />
</div>
</Show>
</div>
);
}
@@ -0,0 +1,137 @@
import { chunkIdToIndex } from "/src/scripts/datasets/resource";
import {
getInitialTimeRange,
setActiveIds,
} from "/src/scripts/lightweightCharts/time";
import { createRWS } from "/src/solid/rws";
import { Chart } from "./chart";
export function Charts({
firstChartSetter,
preset,
datasets,
legendSetter,
dark,
activeIds,
}: {
firstChartSetter: Setter<IChartApi | undefined>;
preset: Accessor<Preset>;
datasets: Datasets;
legendSetter: Setter<SeriesLegend[]>;
dark: Accessor<boolean>;
activeIds: RWS<number[]>;
}) {
const scale = createMemo(() => preset().scale);
const exactRange = createRWS(getInitialTimeRange(scale()));
const priceSeriesType = createRWS<PriceSeriesType>("Candlestick");
const activeDatasets = createRWS([] as ResourceDataset<any, any>[], {
equals: false,
});
const chartSeriesConfigs = createRWS([] as SeriesConfig[][], {
equals: false,
});
const charts = createRWS(
[] as {
chart: RWS<IChartApi | undefined>;
whitespace: RWS<ISeriesApiAny | undefined>;
}[],
{
equals: false,
},
);
const lastChartIndex = createMemo(() => chartSeriesConfigs().length - 1);
const seriesCount = createMemo(() =>
chartSeriesConfigs().reduce(
(acc, l) => (acc += l.length),
1, // Because of price series
),
);
const lastActiveIndex = createMemo(() => {
const last = activeIds().at(-1);
return last !== undefined
? chunkIdToIndex(preset().scale, last)
: undefined;
});
const chartsDrawn = createMemo(() =>
chartSeriesConfigs().map((_) => createRWS(true)),
);
createEffect(
on([activeIds, activeDatasets], ([ids, activeDatasets]) => {
for (let i = 0; i < ids.length; i++) {
const id = ids[i];
for (let j = 0; j < activeDatasets.length; j++) {
activeDatasets[j].fetch(id);
}
}
}),
);
createEffect(
on(preset, (preset) => {
const scale = preset.scale;
exactRange.set(getInitialTimeRange(scale));
chartSeriesConfigs.set(
[preset.top || [], preset.bottom].flatMap((list) =>
list ? [list] : [],
),
);
charts.set(() =>
new Array(chartSeriesConfigs().length).fill(undefined).map(() => ({
chart: createRWS(undefined as IChartApi | undefined),
whitespace: createRWS(undefined as ISeriesApiAny | undefined),
})),
);
setActiveIds({
exactRange: exactRange(),
activeIds,
});
legendSetter(() => []);
}),
);
onCleanup(() => {
firstChartSetter(undefined);
charts().map(({ chart, whitespace }) => {
chart()?.remove();
chart.set(undefined);
whitespace.set(undefined);
});
});
return (
<For
each={chartSeriesConfigs().filter(
(configs, index) => index === 0 || configs.length !== 0,
)}
>
{(seriesConfigs, index) => (
<Chart
activeDatasets={activeDatasets}
activeIds={activeIds}
charts={charts}
chartsDrawn={chartsDrawn}
dark={dark}
datasets={datasets}
exactRange={exactRange}
firstChartSetter={firstChartSetter}
index={index}
lastActiveIndex={lastActiveIndex}
lastChartIndex={lastChartIndex}
legendSetter={legendSetter}
preset={preset}
priceSeriesType={priceSeriesType}
seriesConfigs={seriesConfigs}
seriesCount={seriesCount}
/>
)}
</For>
);
}
@@ -6,26 +6,26 @@ import { GENESIS_DAY } from "../../../../../scripts/lightweightCharts/whitespace
import { Box } from "../../box";
import { Scrollable } from "../../scrollable";
const MULTIPLIER = 0.0025;
const DELAY = 25;
const DELAY = 1;
const MULTIPLIER = DELAY / 1000;
const LEFT = -1;
const RIGHT = 1;
export function TimeScale({
scale,
charts,
firstChart,
}: {
scale: Accessor<ResourceScale>;
charts: RWS<IChartApi[]>;
firstChart: RWS<IChartApi | undefined>;
}) {
const today = new Date();
const disabled = createMemo(() => charts().length === 0);
const disabled = createMemo(() => !firstChart());
const scrollDirection = createRWS(0);
const timeScale = createMemo(() => {
const chart = charts().at(0);
const chart = firstChart();
if (!chart) return undefined;
return chart.timeScale();
});
@@ -86,21 +86,25 @@ export function TimeScale({
<Button
minWidth
disabled={disabled}
onClick={() => setTimeScale({ scale: scale(), charts })}
onClick={() => setTimeScale({ scale: scale(), timeScale })}
>
All Time
</Button>
<Button
minWidth
disabled={disabled}
onClick={() => setTimeScale({ scale: scale(), charts, days: 7 })}
onClick={() =>
setTimeScale({ scale: scale(), timeScale, days: 7 })
}
>
1 Week
</Button>
<Button
minWidth
disabled={disabled}
onClick={() => setTimeScale({ scale: scale(), charts, days: 30 })}
onClick={() =>
setTimeScale({ scale: scale(), timeScale, days: 30 })
}
>
1 Month
</Button>
@@ -108,7 +112,7 @@ export function TimeScale({
minWidth
disabled={disabled}
onClick={() =>
setTimeScale({ scale: scale(), charts, days: 3 * 30 })
setTimeScale({ scale: scale(), timeScale, days: 3 * 30 })
}
>
3 Months
@@ -117,7 +121,7 @@ export function TimeScale({
minWidth
disabled={disabled}
onClick={() =>
setTimeScale({ scale: scale(), charts, days: 6 * 30 })
setTimeScale({ scale: scale(), timeScale, days: 6 * 30 })
}
>
6 Months
@@ -128,7 +132,7 @@ export function TimeScale({
onClick={() =>
setTimeScale({
scale: scale(),
charts,
timeScale,
days: Math.ceil(
(today.getTime() -
new Date(`${today.getUTCFullYear()}-01-01`).getTime()) /
@@ -143,7 +147,7 @@ export function TimeScale({
minWidth
disabled={disabled}
onClick={() =>
setTimeScale({ scale: scale(), charts, days: 365 })
setTimeScale({ scale: scale(), timeScale, days: 365 })
}
>
1 Year
@@ -152,7 +156,7 @@ export function TimeScale({
minWidth
disabled={disabled}
onClick={() =>
setTimeScale({ scale: scale(), charts, days: 2 * 365 })
setTimeScale({ scale: scale(), timeScale, days: 2 * 365 })
}
>
2 Years
@@ -161,7 +165,7 @@ export function TimeScale({
minWidth
disabled={disabled}
onClick={() =>
setTimeScale({ scale: scale(), charts, days: 4 * 365 })
setTimeScale({ scale: scale(), timeScale, days: 4 * 365 })
}
>
4 Years
@@ -170,7 +174,7 @@ export function TimeScale({
minWidth
disabled={disabled}
onClick={() =>
setTimeScale({ scale: scale(), charts, days: 8 * 365 })
setTimeScale({ scale: scale(), timeScale, days: 8 * 365 })
}
>
8 Years
@@ -187,7 +191,9 @@ export function TimeScale({
<Button
minWidth
disabled={disabled}
onClick={() => setTimeScale({ scale: scale(), charts, year })}
onClick={() =>
setTimeScale({ scale: scale(), timeScale, year })
}
>
{year}
</Button>
@@ -214,7 +220,7 @@ export function TimeScale({
onClick={() =>
setTimeScale({
scale: scale(),
charts,
timeScale,
range: {
from: i * 100_000,
to: (i + 0.5) * 100_000,
@@ -282,13 +288,13 @@ function Button({
}
function setTimeScale({
charts,
timeScale,
scale,
days,
year,
range,
}: {
charts: RWS<IChartApi[]>;
timeScale: Accessor<ITimeScaleApi<Time> | undefined>;
scale: ResourceScale;
days?: number;
year?: number;
@@ -307,22 +313,16 @@ function setTimeScale({
from = new Date(GENESIS_DAY);
}
charts()
.at(0)
?.timeScale()
.setVisibleRange({
from: (from.getTime() / 1000) as Time,
to: (to.getTime() / 1000) as Time,
});
timeScale()?.setVisibleRange({
from: (from.getTime() / 1000) as Time,
to: (to.getTime() / 1000) as Time,
});
} else if (scale === "height") {
if (range) {
charts()
.at(0)
?.timeScale()
.setVisibleRange({
from: range.from as Time,
to: range.to as Time,
});
timeScale()?.setVisibleRange({
from: range.from as Time,
to: range.to as Time,
});
}
}
}
+26 -18
View File
@@ -1,4 +1,5 @@
import { classPropToString } from "/src/solid/classes";
import { createWasIdleAccessor } from "/src/solid/idle";
import { createRWS } from "/src/solid/rws";
import { Box } from "../box";
@@ -24,18 +25,18 @@ export function ChartFrame({
dark: Accessor<boolean>;
standalone: boolean;
}) {
const legend = createRWS<SeriesLegend[]>([]);
const legend = createRWS<SeriesLegend[]>([], { equals: false });
const charts = createRWS<IChartApi[]>([]);
const div = createRWS<HTMLDivElement | undefined>(undefined);
const firstChart = createRWS<IChartApi | undefined>(undefined);
const scale = createMemo(() => presets.selected().scale);
const activeIds = createRWS([] as number[], { equals: false });
const Chart = lazy(() =>
import("./components/chart").then((d) => ({ default: d.Chart })),
const wasIdle = createWasIdleAccessor();
const Charts = lazy(() =>
import("./components/charts").then((d) => ({ default: d.Charts })),
);
return (
@@ -49,7 +50,13 @@ export function ChartFrame({
display: (hide ? hide() : false) ? "none" : undefined,
}}
>
<Box flex={false} dark padded={false} classes="short:hidden">
<Box
flex={false}
dark
padded={false}
spaced={false}
classes="short:hidden"
>
<Title presets={presets} />
<div class="border-lighter border-t" />
@@ -68,19 +75,20 @@ export function ChartFrame({
</div>
</Box>
<div ref={div.set} class="-mr-2 -mt-2 flex min-h-0 flex-1 flex-col">
<Chart
parentDiv={div}
charts={charts}
datasets={datasets}
legendSetter={legend.set}
presets={presets}
dark={dark}
activeIds={activeIds}
/>
<div class="-mr-2 -mt-2 flex min-h-0 flex-1 flex-col">
<Show when={wasIdle()}>
<Charts
firstChartSetter={firstChart.set}
datasets={datasets}
legendSetter={legend.set}
preset={presets.selected}
dark={dark}
activeIds={activeIds}
/>
</Show>
</div>
<TimeScale charts={charts} scale={scale} />
<TimeScale firstChart={firstChart} scale={scale} />
</div>
);
}
+52 -29
View File
@@ -34,7 +34,7 @@ export function SettingsFrame({
<div class="space-y-4">
<Title>General</Title>
<RadioGroup
<FieldRadioGroup
title="Theme"
ariaTitle="App's theme"
description="Options for the app's theme"
@@ -47,14 +47,14 @@ export function SettingsFrame({
<div class="space-y-4">
<Title>Background</Title>
<RadioGroup
<FieldRadioGroup
title="Mode"
ariaTitle="Background mode"
description="Options for how the background in displayed"
sl={backgroundMode}
/>
<RadioGroup
<FieldRadioGroup
title="Opacity"
ariaTitle="Background mode"
description="Options for the opacity of the text in the background"
@@ -232,7 +232,7 @@ function Title({ children }: ParentProps) {
return <p class="text-base font-medium">{children}</p>;
}
function RadioGroup<
export function FieldRadioGroup<
T extends
| string
| {
@@ -256,31 +256,54 @@ function RadioGroup<
<p class="pb-1 text-sm opacity-50">{description}</p>
<div class="border-superlight -mx-2 mt-2 flex gap-1.5 rounded-lg border bg-stone-400/30 p-1.5 backdrop-blur-[2px] dark:bg-stone-950/75">
<For each={sl.list()}>
{(value) => (
<label
class={classPropToString([
value === sl.selected()
? "border-lighter bg-orange-50/75 shadow dark:bg-orange-200/10"
: "border-transparent",
"flex flex-1 cursor-pointer select-none items-center justify-center rounded-md border px-3 py-1.5 font-medium hover:bg-orange-50 focus:outline-none active:scale-95 active:bg-orange-50 dark:hover:bg-orange-200/20 dark:active:bg-orange-200/10",
])}
>
<input
type="radio"
name={`${title}-option`}
value={typeof value === "object" ? value.value : value}
class="sr-only"
onClick={() => {
sl.select(value);
}}
/>
<span>{typeof value === "object" ? value.text : value}</span>
</label>
)}
</For>
</div>
<RadioGroup sl={sl} title={title} />
</fieldset>
);
}
export function RadioGroup<
T extends
| string
| {
text: string;
value: number;
},
>({ title, sl, size }: { title: string; sl: SL<T>; size?: Size }) {
return (
<div
class={classPropToString([
size === "xs" && "gap-0.5 rounded-md border p-0.5 text-xs",
size === "sm" && "gap-1 rounded-md border p-1 text-sm",
(!size || size === "base") && "gap-1.5 rounded-lg border p-1.5",
"border-superlight -mx-2 mt-2 flex bg-stone-400/30 backdrop-blur-[2px] dark:bg-stone-950/75",
])}
>
<For each={sl.list()}>
{(value) => (
<label
class={classPropToString([
size === "xs" && "rounded px-1.5 py-0",
size === "sm" && "rounded px-2 py-1",
(!size || size === "base") && "rounded-md px-3 py-1.5",
value === sl.selected()
? "border-lighter bg-orange-50/75 shadow dark:bg-orange-200/10"
: "border-transparent",
"flex flex-1 cursor-pointer select-none items-center justify-center border font-medium hover:bg-orange-50 focus:outline-none active:scale-95 active:bg-orange-50 dark:hover:bg-orange-200/20 dark:active:bg-orange-200/10",
])}
>
<input
type="radio"
name={`${title}-option`}
value={typeof value === "object" ? value.value : value}
class="sr-only"
onClick={() => {
sl.select(value);
}}
/>
<span>{typeof value === "object" ? value.text : value}</span>
</label>
)}
</For>
</div>
);
}
+2
View File
@@ -5,3 +5,5 @@ type FrameName =
| "Search"
| "History"
| "Settings";
type Size = "xs" | "sm" | "base" | "lg" | "xl";
+9 -13
View File
@@ -8,17 +8,15 @@ import { colors } from "../utils/colors";
import { valueToString } from "../utils/locale";
import { HorzScaleBehaviorHeight } from "./horzScaleBehavior";
export function createChart(
scale: ResourceScale,
element: HTMLElement,
{
dark,
priceScaleOptions,
}: {
dark: Accessor<boolean>;
priceScaleOptions?: DeepPartialPriceScaleOptions;
},
) {
export function createChart({
scale,
element,
dark,
}: {
scale: ResourceScale;
element: HTMLElement;
dark: Accessor<boolean>;
}) {
console.log(`chart: create (scale: ${scale})`);
const options: DeepPartialChartOptions = {
@@ -63,11 +61,9 @@ export function createChart(
}
chart.priceScale("right").applyOptions({
...priceScaleOptions,
scaleMargins: {
top: 0.075,
bottom: 0.075,
...priceScaleOptions?.scaleMargins,
},
minimumWidth: 78,
});
+209
View File
@@ -0,0 +1,209 @@
import { createRWS } from "/src/solid/rws";
import { chunkIdToIndex } from "../datasets/resource";
import { SeriesType } from "../presets/enums";
import { stringToId } from "../utils/id";
import { createBaseLineSeries, DEFAULT_BASELINE_COLORS } from "./baseLine";
import { createCandlesticksSeries } from "./candlesticks";
import { createHistogramSeries } from "./histogram";
import { createSeriesLegend } from "./legend";
import { createLineSeries } from "./line";
export function createSeriesGroup<Scale extends ResourceScale>({
scale,
datasets,
activeIds,
seriesConfig,
preset,
chartLegend,
chart,
index: seriesIndex,
disabled,
lastActiveIndex,
debouncedSetMinMaxMarkers,
dark,
}: {
scale: Scale;
datasets: Datasets;
activeIds: Accessor<number[]>;
seriesConfig: SeriesConfig;
preset: Preset;
chart: IChartApi;
index: number;
chartLegend: SeriesLegend[];
lastActiveIndex: Accessor<number | undefined>;
disabled?: Accessor<boolean>;
debouncedSetMinMaxMarkers: VoidFunction;
dark: Accessor<boolean>;
}) {
const {
datasetPath,
title,
colors,
color,
defaultVisible,
seriesType: type,
options,
priceScaleOptions,
} = seriesConfig;
const dataset = datasets.getOrImport(
scale,
datasetPath as DatasetPath<Scale>,
);
const seriesList: RWS<
ISeriesApi<"Baseline" | "Line" | "Histogram" | "Candlestick"> | undefined
>[] = new Array(dataset.fetchedJSONs.length);
const legend = createSeriesLegend({
scale,
id: stringToId(title),
presetId: preset.id,
title,
seriesList,
color: colors || color || DEFAULT_BASELINE_COLORS,
defaultVisible,
disabled,
dataset,
});
chartLegend.push(legend);
dataset.fetchedJSONs.forEach((json, index) => {
const series: (typeof seriesList)[number] = createRWS(undefined);
seriesList[index] = series;
createEffect(() => {
const values = json.vec();
if (!values) return;
if (seriesIndex > 0) {
let previous = chartLegend.at(seriesIndex - 1)?.seriesList[index];
if (!previous?.()) {
return;
}
}
untrack(() => {
let s = series();
if (!s) {
switch (type) {
case SeriesType.Based: {
s = createBaseLineSeries({
chart,
dark,
color,
topColor: seriesConfig.topColor,
bottomColor: seriesConfig.bottomColor,
options,
});
break;
}
case SeriesType.Candlestick: {
const candlestickSeries = createCandlesticksSeries({
chart,
options,
dark,
});
s = candlestickSeries[0];
if (!colors && !color) {
legend.color = candlestickSeries[1];
}
break;
}
case SeriesType.Histogram: {
s = createHistogramSeries({
chart,
options,
});
break;
}
default:
case SeriesType.Line: {
s = createLineSeries({
chart,
color,
dark,
options,
});
break;
}
}
if (priceScaleOptions) {
s.priceScale().applyOptions(priceScaleOptions);
}
series.set(s);
}
s.setData(values);
debouncedSetMinMaxMarkers();
});
});
createEffect(() => {
const s = series();
const currentVec = dataset.fetchedJSONs.at(index)?.vec();
const nextVec = dataset.fetchedJSONs.at(index + 1)?.vec();
if (s && currentVec?.length && nextVec?.length) {
s.update(nextVec[0]);
}
});
const isLast = createMemo(() => {
const last = lastActiveIndex();
return last !== undefined && last === index;
});
createEffect(() => {
series()?.applyOptions({
lastValueVisible: legend.drawn() && isLast(),
});
});
const inRange = createMemo(() => {
const range = activeIds();
if (range.length) {
const start = chunkIdToIndex(scale, range.at(0)!);
const end = chunkIdToIndex(scale, range.at(-1)!);
if (index >= start && index <= end) {
return true;
}
}
return false;
});
const visible = createMemo((previous: boolean) => {
if (legend.disabled()) {
return false;
}
return previous || inRange();
}, false);
createEffect(() => {
series()?.applyOptions({
visible: legend.drawn() && visible(),
});
});
});
return legend;
}
@@ -0,0 +1,48 @@
import { dateFromTime, getNumberOfDaysBetweenTwoDates } from "../utils/date";
import { debounce } from "../utils/debounce";
export const debouncedUpdateVisiblePriceSeriesType = debounce(
updateVisiblePriceSeriesType,
50,
);
export function updateVisiblePriceSeriesType({
scale,
chart,
logicalRange,
timeRange,
priceSeriesType,
}: {
scale: ResourceScale;
chart: IChartApi;
logicalRange?: LogicalRange;
timeRange?: TimeRange;
priceSeriesType: RWS<PriceSeriesType>;
}) {
try {
const width = chart.timeScale().width();
let ratio: number;
if (logicalRange) {
ratio = (logicalRange.to - logicalRange.from) / width;
} else if (timeRange) {
if (scale === "date") {
ratio = getNumberOfDaysBetweenTwoDates(
dateFromTime(timeRange.from),
dateFromTime(timeRange.to),
);
} else {
ratio = ((timeRange.to as number) - (timeRange.from as number)) / width;
}
} else {
throw Error();
}
if (ratio <= 0.5) {
priceSeriesType.set("Candlestick");
} else {
priceSeriesType.set("Line");
}
} catch {}
}
+6 -1
View File
@@ -15,6 +15,7 @@ export function createPresets(scale: ResourceScale): PartialPresetFolder {
name: `Total Non Empty Addresses`,
title: `Total Non Empty Address`,
description: "",
unit: "Count",
icon: IconTablerWallet,
bottom: [
{
@@ -29,6 +30,7 @@ export function createPresets(scale: ResourceScale): PartialPresetFolder {
name: `New Addresses`,
title: `New Addresses`,
description: "",
unit: "Count",
icon: IconTablerSparkles,
bottom: [
{
@@ -43,6 +45,7 @@ export function createPresets(scale: ResourceScale): PartialPresetFolder {
name: `Total Addresses Created`,
title: `Total Addresses Created`,
description: "",
unit: "Count",
icon: IconTablerArchive,
bottom: [
{
@@ -57,6 +60,7 @@ export function createPresets(scale: ResourceScale): PartialPresetFolder {
name: `Total Empty Addresses`,
title: `Total Empty Addresses`,
description: "",
unit: "Count",
icon: IconTablerTrash,
bottom: [
{
@@ -177,8 +181,9 @@ export function createAddressCountPreset({
scale,
name: `Address Count`,
title: `${name} Address Count`,
icon: IconTablerAddressBook,
description: "",
unit: "Count",
icon: IconTablerAddressBook,
bottom: [addressCount],
};
}
-669
View File
@@ -1,669 +0,0 @@
import { requestIdleCallbackPossible } from "/src/env";
import { createRWS } from "/src/solid/rws";
import { chunkIdToIndex } from "../datasets/resource";
import {
createBaseLineSeries,
DEFAULT_BASELINE_COLORS,
} from "../lightweightCharts/baseLine";
import { createCandlesticksSeries } from "../lightweightCharts/candlesticks";
import { createChart } from "../lightweightCharts/create";
import { createHistogramSeries } from "../lightweightCharts/histogram";
import { createSeriesLegend } from "../lightweightCharts/legend";
import { createLineSeries } from "../lightweightCharts/line";
import { setMinMaxMarkers } from "../lightweightCharts/markers";
import {
getInitialTimeRange,
initTimeScale,
setActiveIds,
setInitialTimeRange,
} from "../lightweightCharts/time";
import { setWhitespace } from "../lightweightCharts/whitespace";
import { colors } from "../utils/colors";
import { dateFromTime, getNumberOfDaysBetweenTwoDates } from "../utils/date";
import { debounce } from "../utils/debounce";
import { stringToId } from "../utils/id";
import { webSockets } from "../ws";
import { SeriesType } from "./enums";
export function applySeriesList({
parentDiv,
charts: reactiveChartList,
top,
bottom,
preset,
priceScaleOptions,
datasets,
priceDataset,
priceOptions,
legendSetter,
dark,
activeIds,
}: {
charts: RWS<IChartApi[]>;
parentDiv: HTMLDivElement;
preset: Preset;
legendSetter: Setter<SeriesLegend[]>;
priceDataset?: AnyDatasetPath;
priceOptions?: PriceSeriesOptions;
// priceScaleOptions?: DeepPartialPriceScaleOptions;
// top?: SeriesConfig<Scale>[];
// bottom?: SeriesConfig<Scale>[];
datasets: Datasets;
dark: Accessor<boolean>;
activeIds: RWS<number[]>;
} & PresetParams) {
// ---
// Reset states
// ---
legendSetter([]);
reactiveChartList.set((charts) => {
charts.forEach((chart) => {
chart.remove();
});
return [];
});
parentDiv.replaceChildren();
// ---
// Done
// ---
const scale = preset.scale;
const presetLegend: SeriesLegend[] = [];
const priceSeriesType = createRWS<PriceSeriesType>("Candlestick");
const activeDatasets: ResourceDataset<any, any>[] = [];
const lastActiveIndex = createMemo(() => {
const last = activeIds().at(-1);
return last !== undefined ? chunkIdToIndex(scale, last) : undefined;
});
const exactRange = createRWS(getInitialTimeRange(scale));
setActiveIds({
exactRange: exactRange(),
activeIds: activeIds,
});
const seriesNumber = 1 + (top || []).length + (bottom || []).length;
const charts = [top || [], bottom]
.flatMap((list) => (list ? [list] : []))
.flatMap((seriesConfigList, chartIndex) => {
if (chartIndex !== 0 && seriesConfigList.length === 0) {
return [];
}
const div = document.createElement("div");
div.className = "w-full cursor-crosshair min-h-0 border-lighter h-full";
parentDiv.appendChild(div);
const chart = createChart(scale, div, {
dark,
priceScaleOptions,
});
if (!chart) {
console.log("chart: undefined");
return [];
}
const whitespace = setWhitespace(chart, scale);
const range = exactRange();
setInitialTimeRange({ chart, range });
if (chartIndex === 0) {
initTimeScale({
scale,
chart,
activeIds: activeIds,
exactRange,
});
if (range) {
updateVisiblePriceSeriesType({
scale,
chart,
priceSeriesType,
timeRange: range,
});
}
}
// const whitespace = new Array<ISeriesApi<"Line"> | undefined>(
// scale === "date"
// ? whitespaceDateDatasets.length
// : whitespaceHeightDatasets.length,
// ).fill(undefined);
// function createWhitespaceSeriesIfNeeded(index: number) {
// console.log(index);
// if (index >= 0 && index < whitespace.length && !whitespace[index]) {
// const series = createLineSeries(chart);
// whitespace[index] = series;
// if (scale === "date") {
// series.setData(whitespaceDateDatasets[index]);
// } else {
// series.setData(whitespaceHeightDatasets[index]);
// }
// }
// }
// createEffect(() => {
// const ids = activeIds();
// console.log(ids);
// const idsLength = ids.length;
// for (let i = 0; i < idsLength; i++) {
// const id = ids[i];
// const whitespaceIndex = chunkIdToIndex(
// scale,
// scale === "date"
// ? id - whitespaceStartDateYear
// : id - whitespaceHeightStart,
// );
// if (i === 0) {
// createWhitespaceSeriesIfNeeded(whitespaceIndex - 1);
// }
// createWhitespaceSeriesIfNeeded(whitespaceIndex);
// if (i === idsLength - 1) {
// createWhitespaceSeriesIfNeeded(whitespaceIndex + 1);
// }
// }
// });
const chartLegend: SeriesLegend[] = [];
onCleanup(() => {
chartLegend.length = 0;
});
const markerCallback = () =>
setMinMaxMarkers({
scale,
visibleRange: exactRange(),
legendList: chartLegend,
dark,
activeIds: activeIds,
});
const debouncedSetMinMaxMarkers = requestIdleCallbackPossible
? () => requestIdleCallback(markerCallback)
: debounce(
markerCallback,
seriesNumber * 10 + scale === "date" ? 50 : 100,
);
createEffect(on([exactRange, dark], debouncedSetMinMaxMarkers));
if (chartIndex === 0) {
const datasetPath =
priceDataset || (`/${scale}-to-price` satisfies AnyDatasetPath);
const dataset = datasets.getOrImport(scale, datasetPath);
activeDatasets.push(dataset);
const title = priceOptions?.title || "Price";
const priceScaleOptions: DeepPartialPriceScaleOptions = {
mode: 1,
...priceOptions?.priceScaleOptions,
};
function createPriceSeries(seriesType: PriceSeriesType) {
let seriesConfig: SeriesConfig;
if (seriesType === "Candlestick") {
seriesConfig = {
// @ts-ignore
datasetPath,
title,
seriesType: SeriesType.Candlestick,
options: priceOptions,
priceScaleOptions,
};
} else {
seriesConfig = {
// @ts-ignore
datasetPath,
title,
color: colors.white,
options: priceOptions?.seriesOptions,
priceScaleOptions,
};
}
const priceSeries = createSeriesGroup({
scale,
datasets,
index: -1,
activeIds,
seriesConfig,
chart,
chartLegend,
lastActiveIndex,
preset,
disabled: () => priceSeriesType() !== seriesType,
debouncedSetMinMaxMarkers,
dark,
});
createEffect(() => {
const latest = webSockets.liveKrakenCandle.latest();
if (!latest) return;
const index = chunkIdToIndex(scale, latest.year);
const series = priceSeries.seriesList.at(index)?.();
series?.update(latest);
});
return priceSeries;
}
const priceCandlestickLegend = createPriceSeries("Candlestick");
const priceLineLegend = createPriceSeries("Line");
createEffect(() => {
priceCandlestickLegend.visible.set(priceLineLegend.visible());
});
createEffect(() => {
priceLineLegend.visible.set(priceCandlestickLegend.visible());
});
}
[...seriesConfigList].reverse().forEach((seriesConfig, index) => {
const dataset = datasets.getOrImport(scale, seriesConfig.datasetPath);
activeDatasets.push(dataset);
createSeriesGroup({
scale,
datasets,
activeIds: activeIds,
index,
seriesConfig,
chartLegend,
chart,
preset,
lastActiveIndex,
debouncedSetMinMaxMarkers,
dark,
});
});
chartLegend.forEach((legend) => {
presetLegend.splice(0, 0, legend);
createEffect(on(legend.visible, debouncedSetMinMaxMarkers));
});
return [
{
scale,
div,
chart,
whitespace,
legendList: chartLegend,
debouncedSetMinMaxMarkers,
},
];
}) satisfies ChartObject[];
createEffect(() => {
const visibleCharts: typeof charts = [];
charts.forEach((chart) => {
if (chart.legendList.some((legend) => legend.drawn())) {
chart.div.style.border = "";
chart.div.style.maxHeight = "100%";
visibleCharts.push(chart);
} else {
// chart.div.style.height = "100%";
chart.div.style.maxHeight = "0px";
chart.div.style.border = "none";
}
});
visibleCharts.forEach(({ div, chart }, index) => {
const last = index === visibleCharts.length - 1;
div.style.height = last ? "100%" : "calc(100% - 62px)";
div.style.borderBottomWidth = last ? "none" : "1px";
div.style.marginBottom = last ? "" : "-2px";
chart.timeScale().applyOptions({
visible: last,
});
});
});
const debouncedUpdateVisiblePriceSeriesType = debounce(
updateVisiblePriceSeriesType,
50,
);
const activeDatasetsLength = activeDatasets.length;
createEffect(() => {
const range = activeIds();
untrack(() => {
for (let i = 0; i < range.length; i++) {
const id = range[i];
for (let j = 0; j < activeDatasetsLength; j++) {
activeDatasets[j].fetch(id);
}
}
});
});
const lastChartIndex = charts.length - 1;
for (let i = 0; i < charts.length; i++) {
const chart = charts[i].chart;
chart.timeScale().subscribeVisibleLogicalRangeChange((logicalRange) => {
if (!logicalRange) return;
// Must be the chart with the visible timeScale
if (i === lastChartIndex) {
debouncedUpdateVisiblePriceSeriesType({
scale,
chart,
logicalRange,
priceSeriesType,
});
}
for (let j = 0; j <= lastChartIndex; j++) {
if (i !== j) {
charts[j].chart.timeScale().setVisibleLogicalRange(logicalRange);
}
}
});
chart.subscribeCrosshairMove(({ time, sourceEvent }) => {
// Don't override crosshair position from scroll event
if (time && !sourceEvent) return;
for (let j = 0; j <= lastChartIndex; j++) {
const whitespace = charts[j].whitespace;
const otherChart = charts[j].chart;
if (whitespace && i !== j) {
if (time) {
otherChart.setCrosshairPosition(NaN, time, whitespace);
} else {
// No time when mouse goes outside the chart
otherChart.clearCrosshairPosition();
}
}
}
});
}
legendSetter(presetLegend);
reactiveChartList.set(() => charts.map(({ chart }) => chart));
}
export function updateVisiblePriceSeriesType({
scale,
chart,
logicalRange,
timeRange,
priceSeriesType,
}: {
scale: ResourceScale;
chart: IChartApi;
logicalRange?: LogicalRange;
timeRange?: TimeRange;
priceSeriesType: RWS<PriceSeriesType>;
}) {
try {
const width = chart.timeScale().width();
let ratio: number;
if (logicalRange) {
ratio = (logicalRange.to - logicalRange.from) / width;
} else if (timeRange) {
if (scale === "date") {
ratio = getNumberOfDaysBetweenTwoDates(
dateFromTime(timeRange.from),
dateFromTime(timeRange.to),
);
} else {
ratio = ((timeRange.to as number) - (timeRange.from as number)) / width;
}
} else {
throw Error();
}
if (ratio <= 0.5) {
priceSeriesType.set("Candlestick");
} else {
priceSeriesType.set("Line");
}
} catch {}
}
function createSeriesGroup<Scale extends ResourceScale>({
scale,
datasets,
activeIds,
seriesConfig,
preset,
chartLegend,
chart,
index: seriesIndex,
disabled,
lastActiveIndex,
debouncedSetMinMaxMarkers,
dark,
}: {
scale: Scale;
datasets: Datasets;
activeIds: Accessor<number[]>;
seriesConfig: SeriesConfig;
preset: Preset;
chart: IChartApi;
index: number;
chartLegend: SeriesLegend[];
lastActiveIndex: Accessor<number | undefined>;
disabled?: Accessor<boolean>;
debouncedSetMinMaxMarkers: VoidFunction;
dark: Accessor<boolean>;
}) {
const {
datasetPath,
title,
colors,
color,
defaultVisible,
seriesType: type,
options,
priceScaleOptions,
} = seriesConfig;
const dataset = datasets.getOrImport(
scale,
datasetPath as DatasetPath<Scale>,
);
const seriesList: RWS<
ISeriesApi<"Baseline" | "Line" | "Histogram" | "Candlestick"> | undefined
>[] = new Array(dataset.fetchedJSONs.length);
const legend = createSeriesLegend({
scale,
id: stringToId(title),
presetId: preset.id,
title,
seriesList,
color: colors || color || DEFAULT_BASELINE_COLORS,
defaultVisible,
disabled,
dataset,
});
chartLegend.push(legend);
dataset.fetchedJSONs.forEach((json, index) => {
const series: (typeof seriesList)[number] = createRWS(undefined);
seriesList[index] = series;
createEffect(() => {
const values = json.vec();
if (!values) return;
if (seriesIndex > 0) {
let previous = chartLegend.at(seriesIndex - 1)?.seriesList[index];
if (!previous?.()) {
return;
}
}
untrack(() => {
let s = series();
if (!s) {
switch (type) {
case SeriesType.Based: {
s = createBaseLineSeries({
chart,
dark,
color,
topColor: seriesConfig.topColor,
bottomColor: seriesConfig.bottomColor,
options,
});
break;
}
case SeriesType.Candlestick: {
const candlestickSeries = createCandlesticksSeries({
chart,
options,
dark,
});
s = candlestickSeries[0];
if (!colors && !color) {
legend.color = candlestickSeries[1];
}
break;
}
case SeriesType.Histogram: {
s = createHistogramSeries({
chart,
options,
});
break;
}
default:
case SeriesType.Line: {
s = createLineSeries({
chart,
color,
dark,
options,
});
break;
}
}
if (priceScaleOptions) {
s.priceScale().applyOptions(priceScaleOptions);
}
series.set(s);
}
s.setData(values);
debouncedSetMinMaxMarkers();
});
});
createEffect(() => {
const s = series();
const currentVec = dataset.fetchedJSONs.at(index)?.vec();
const nextVec = dataset.fetchedJSONs.at(index + 1)?.vec();
if (s && currentVec?.length && nextVec?.length) {
s.update(nextVec[0]);
}
});
const isLast = createMemo(() => {
const last = lastActiveIndex();
return last !== undefined && last === index;
});
createEffect(() => {
series()?.applyOptions({
lastValueVisible: legend.drawn() && isLast(),
});
});
const inRange = createMemo(() => {
const range = activeIds();
if (range.length) {
const start = chunkIdToIndex(scale, range.at(0)!);
const end = chunkIdToIndex(scale, range.at(-1)!);
if (index >= start && index <= end) {
return true;
}
}
return false;
});
const visible = createMemo((previous: boolean) => {
if (legend.disabled()) {
return false;
}
return previous || inRange();
}, false);
createEffect(() => {
series()?.applyOptions({
visible: legend.drawn() && visible(),
});
});
});
return legend;
}
+19 -4
View File
@@ -13,6 +13,7 @@ export function createPresets(scale: ResourceScale) {
name: "Height",
title: "Block Height",
description: "",
unit: "Height",
bottom: [
{
title: "Height",
@@ -31,6 +32,7 @@ export function createPresets(scale: ResourceScale) {
name: "Daily Sum",
title: "Daily Sum Of Blocks Mined",
description: "",
unit: "Count",
bottom: [
{
title: "Target",
@@ -64,6 +66,7 @@ export function createPresets(scale: ResourceScale) {
name: "Weekly Sum",
title: "Weekly Sum Of Blocks Mined",
description: "",
unit: "Count",
bottom: [
{
title: "Target",
@@ -86,6 +89,7 @@ export function createPresets(scale: ResourceScale) {
name: "Monthly Sum",
title: "Monthly Sum Of Blocks Mined",
description: "",
unit: "Count",
bottom: [
{
title: "Target",
@@ -108,6 +112,7 @@ export function createPresets(scale: ResourceScale) {
name: "Yearly Sum",
title: "Yearly Sum Of Blocks Mined",
description: "",
unit: "Count",
bottom: [
{
title: "Target",
@@ -130,6 +135,7 @@ export function createPresets(scale: ResourceScale) {
name: "Total",
title: "Total Blocks Mined",
description: "",
unit: "Count",
bottom: [
{
title: "Mined",
@@ -147,6 +153,7 @@ export function createPresets(scale: ResourceScale) {
scale,
title: "Block Size",
color: colors.darkWhite,
unit: "Megabytes",
keySum: "/date-to-block-size-1d-sum",
keyAverage: "/date-to-block-size-1d-average",
keyMax: "/date-to-block-size-1d-max",
@@ -165,6 +172,7 @@ export function createPresets(scale: ResourceScale) {
scale,
title: "Block Weight",
color: colors.darkWhite,
unit: "Weight",
keyAverage: "/date-to-block-weight-1d-average",
keyMax: "/date-to-block-weight-1d-max",
key90p: "/date-to-block-weight-1d-90p",
@@ -182,6 +190,7 @@ export function createPresets(scale: ResourceScale) {
scale,
title: "Block VBytes",
color: colors.darkWhite,
unit: "Virtual Bytes",
keyAverage: "/date-to-block-vbytes-1d-average",
keyMax: "/date-to-block-vbytes-1d-max",
key90p: "/date-to-block-vbytes-1d-90p",
@@ -199,6 +208,7 @@ export function createPresets(scale: ResourceScale) {
scale,
title: "Block Interval",
color: colors.darkWhite,
unit: "Seconds",
keyAverage: "/date-to-block-interval-1d-average",
keyMax: "/date-to-block-interval-1d-max",
key90p: "/date-to-block-interval-1d-90p",
@@ -217,9 +227,10 @@ export function createPresets(scale: ResourceScale) {
name: "Size",
title: "Block Size",
description: "",
unit: "Megabytes",
bottom: [
{
title: "Size (MB)",
title: "Size",
color: colors.darkWhite,
datasetPath: `/height-to-block-size`,
},
@@ -231,9 +242,10 @@ export function createPresets(scale: ResourceScale) {
name: "Weight",
title: "Block Weight",
description: "",
unit: "Weight",
bottom: [
{
title: "Weight (MB)",
title: "Weight",
color: colors.darkWhite,
datasetPath: `/height-to-block-weight`,
},
@@ -245,6 +257,7 @@ export function createPresets(scale: ResourceScale) {
name: "VBytes",
title: "Block VBytes",
description: "",
unit: "Virtual Bytes",
bottom: [
{
title: "VBytes",
@@ -259,9 +272,10 @@ export function createPresets(scale: ResourceScale) {
name: "Interval",
title: "Block Interval",
description: "",
unit: "Seconds",
bottom: [
{
title: "Interval (s)",
title: "Interval",
color: colors.darkWhite,
datasetPath: `/height-to-block-interval`,
},
@@ -274,9 +288,10 @@ export function createPresets(scale: ResourceScale) {
name: "Cumulative Size",
title: "Cumulative Block Size",
description: "",
unit: "Megabytes",
bottom: [
{
title: "Size (MB)",
title: "Size",
color: colors.darkWhite,
datasetPath: `/${scale}-to-cumulative-block-size`,
},
+37 -23
View File
@@ -15,6 +15,7 @@ export function createPresets(scale: ResourceScale) {
name: "All",
title: "All Cointime Prices",
description: "",
unit: "US Dollars",
top: [
{
title: "Vaulted Price",
@@ -52,6 +53,7 @@ export function createPresets(scale: ResourceScale) {
name: "Price",
title: "Active Price",
description: "",
unit: "US Dollars",
top: [
{
title: "Active Price",
@@ -78,6 +80,7 @@ export function createPresets(scale: ResourceScale) {
name: "Price",
title: "Vaulted Price",
description: "",
unit: "US Dollars",
top: [
{
title: "Vaulted Price",
@@ -104,6 +107,7 @@ export function createPresets(scale: ResourceScale) {
name: "Price",
title: "True Market Mean",
description: "",
unit: "US Dollars",
top: [
{
title: "True Market Mean",
@@ -130,6 +134,7 @@ export function createPresets(scale: ResourceScale) {
name: "Price",
title: "Cointime Price",
description: "",
unit: "US Dollars",
top: [
{
title: "Cointime",
@@ -158,9 +163,7 @@ export function createPresets(scale: ResourceScale) {
name: "All",
title: "Cointime Capitalizations",
description: "",
priceScaleOptions: {
mode: 1,
},
unit: "US Dollars",
bottom: [
{
title: "Market Cap",
@@ -190,9 +193,7 @@ export function createPresets(scale: ResourceScale) {
name: "Thermo Cap",
title: "Thermo Cap",
description: "",
priceScaleOptions: {
mode: 1,
},
unit: "US Dollars",
bottom: [
{
title: "Thermo Cap",
@@ -207,10 +208,7 @@ export function createPresets(scale: ResourceScale) {
name: "Investor Cap",
title: "Investor Cap",
description: "",
priceScaleOptions: {
mode: 1,
},
unit: "US Dollars",
bottom: [
{
title: "Investor Cap",
@@ -223,8 +221,9 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerDivide,
name: "Thermo Cap To Investor Cap Ratio",
title: "Thermo Cap To Investor Cap Ratio (%)",
title: "Thermo Cap To Investor Cap Ratio",
description: "",
unit: "Percentage",
bottom: [
{
title: "Ratio",
@@ -244,6 +243,7 @@ export function createPresets(scale: ResourceScale) {
name: "All",
title: "All Coinblocks",
description: "",
unit: "Coinblocks",
bottom: [
{
title: "Coinblocks Created",
@@ -268,6 +268,7 @@ export function createPresets(scale: ResourceScale) {
name: "Created",
title: "Coinblocks Created",
description: "",
unit: "Coinblocks",
bottom: [
{
title: "Coinblocks Created",
@@ -282,7 +283,7 @@ export function createPresets(scale: ResourceScale) {
name: "Destroyed",
title: "Coinblocks Destroyed",
description: "",
unit: "Coinblocks",
bottom: [
{
title: "Coinblocks Destroyed",
@@ -297,6 +298,7 @@ export function createPresets(scale: ResourceScale) {
name: "Stored",
title: "Coinblocks Stored",
description: "",
unit: "Coinblocks",
bottom: [
{
title: "Coinblocks Stored",
@@ -316,6 +318,7 @@ export function createPresets(scale: ResourceScale) {
name: "All",
title: "All Cumulative Coinblocks",
description: "",
unit: "Coinblocks",
bottom: [
{
title: "Cumulative Coinblocks Created",
@@ -340,6 +343,7 @@ export function createPresets(scale: ResourceScale) {
name: "Created",
title: "Cumulative Coinblocks Created",
description: "",
unit: "Coinblocks",
bottom: [
{
title: "Cumulative Coinblocks Created",
@@ -354,6 +358,7 @@ export function createPresets(scale: ResourceScale) {
name: "Destroyed",
title: "Cumulative Coinblocks Destroyed",
description: "",
unit: "Coinblocks",
bottom: [
{
title: "Cumulative Coinblocks Destroyed",
@@ -368,6 +373,7 @@ export function createPresets(scale: ResourceScale) {
name: "Stored",
title: "Cumulative Coinblocks Stored",
description: "",
unit: "Coinblocks",
bottom: [
{
title: "Cumulative Coinblocks Stored",
@@ -387,6 +393,7 @@ export function createPresets(scale: ResourceScale) {
name: "Liveliness - Activity",
title: "Liveliness (Activity)",
description: "",
unit: "",
bottom: [
{
title: "Liveliness",
@@ -401,6 +408,7 @@ export function createPresets(scale: ResourceScale) {
name: "Vaultedness",
title: "Vaultedness",
description: "",
unit: "",
bottom: [
{
title: "Vaultedness",
@@ -415,6 +423,7 @@ export function createPresets(scale: ResourceScale) {
name: "Versus",
title: "Liveliness V. Vaultedness",
description: "",
unit: "",
bottom: [
{
title: "Liveliness",
@@ -434,6 +443,7 @@ export function createPresets(scale: ResourceScale) {
name: "Activity To Vaultedness Ratio",
title: "Activity To Vaultedness Ratio",
description: "",
unit: "Percentage",
bottom: [
{
title: "Activity To Vaultedness Ratio",
@@ -448,15 +458,16 @@ export function createPresets(scale: ResourceScale) {
name: "Concurrent Liveliness - Supply Adjusted Coindays Destroyed",
title: "Concurrent Liveliness - Supply Adjusted Coindays Destroyed",
description: "",
unit: "",
bottom: [
{
title: "Concurrent Liveliness 14d Median",
color: colors.darkLiveliness,
color: colors.liveliness,
datasetPath: `/${scale}-to-concurrent-liveliness-2w-median`,
},
{
title: "Concurrent Liveliness",
color: colors.liveliness,
color: colors.darkLiveliness,
datasetPath: `/${scale}-to-concurrent-liveliness`,
},
],
@@ -467,6 +478,7 @@ export function createPresets(scale: ResourceScale) {
name: "Liveliness Incremental Change",
title: "Liveliness Incremental Change",
description: "",
unit: "",
bottom: [
{
title: "Liveliness Incremental Change",
@@ -493,6 +505,7 @@ export function createPresets(scale: ResourceScale) {
name: "Vaulted",
title: "Vaulted Supply",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Vaulted Supply",
@@ -507,7 +520,7 @@ export function createPresets(scale: ResourceScale) {
name: "Active",
title: "Active Supply",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Active Supply",
@@ -522,7 +535,7 @@ export function createPresets(scale: ResourceScale) {
name: "Vaulted V. Active",
title: "Vaulted V. Active",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Circulating Supply",
@@ -577,6 +590,7 @@ export function createPresets(scale: ResourceScale) {
name: "Vaulted Net Change",
title: "Vaulted Supply Net Change",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Vaulted Supply Net Change",
@@ -591,6 +605,7 @@ export function createPresets(scale: ResourceScale) {
name: "Active Net Change",
title: "Active Supply Net Change",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Active Supply Net Change",
@@ -605,6 +620,7 @@ export function createPresets(scale: ResourceScale) {
name: "Active VS. Vaulted 90D Net Change",
title: "Active VS. Vaulted 90 Day Supply Net Change",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Active Supply Net Change",
@@ -724,6 +740,7 @@ export function createPresets(scale: ResourceScale) {
name: "In Profit",
title: "Cointime Supply In Profit",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Circulating Supply",
@@ -748,6 +765,7 @@ export function createPresets(scale: ResourceScale) {
name: "In Loss",
title: "Cointime Supply In Loss",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Circulating Supply",
@@ -772,11 +790,9 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerBuildingFactory,
name: "Cointime Yearly Inflation Rate",
title: "Cointime-Adjusted Yearly Inflation Rate (%)",
title: "Cointime-Adjusted Yearly Inflation Rate",
description: "",
priceScaleOptions: {
mode: 1,
},
unit: "Percentage",
bottom: [
{
title: "Cointime Adjusted",
@@ -796,9 +812,7 @@ export function createPresets(scale: ResourceScale) {
name: "Cointime Velocity",
title: "Cointime-Adjusted Transactions Velocity",
description: "",
priceScaleOptions: {
mode: 1,
},
unit: "",
bottom: [
{
title: "Cointime Adjusted",
+1
View File
@@ -18,6 +18,7 @@ export function createPresets(scale: ResourceScale) {
title: `Hodl Supply`,
description: "",
icon: IconTablerRipple,
unit: "Bitcoin",
bottom: [
{
title: `24h`,
@@ -1,7 +1,6 @@
import { averages } from "/src/scripts/datasets/date";
import { colors } from "/src/scripts/utils/colors";
import { SeriesType } from "../../enums";
import { createRatioFolder } from "../../templates/ratio";
export function createPresets(scale: ResourceScale): PartialPresetFolder {
@@ -14,6 +13,7 @@ export function createPresets(scale: ResourceScale): PartialPresetFolder {
name: "All",
title: "All Moving Averages",
description: "",
unit: "US Dollars",
top: averages.map((average) => ({
title: average.key.toUpperCase(),
color: colors[`_${average.key}`],
@@ -52,9 +52,10 @@ function createPresetFolder({
{
scale,
name: "Average",
description: "",
icon: IconTablerMathAvg,
title,
description: "",
unit: "US Dollars",
icon: IconTablerMathAvg,
top: [
{
title: `SMA`,
+2
View File
@@ -13,6 +13,7 @@ export function createPresets(scale: ResourceScale) {
name: "Price",
title: "Market Price",
description: "",
unit: "US Dollars",
},
{
scale,
@@ -20,6 +21,7 @@ export function createPresets(scale: ResourceScale) {
name: "Capitalization",
title: "Market Capitalization",
description: "",
unit: "US Dollars",
bottom: [
{
title: "Market Cap.",
@@ -56,9 +56,10 @@ function createPreset({
description: "",
icon: IconTablerReceiptTax,
title: `${title} Return`,
unit: "Percentage",
bottom: [
{
title: `Return (%)`,
title: `Return`,
seriesType: SeriesType.Based,
datasetPath: `/date-to-price-${key}-return`,
},
+83 -68
View File
@@ -17,8 +17,9 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerCoinBitcoin,
name: "In Bitcoin",
title: "Last Coinbase (In Bitcoin)",
title: "Last Coinbase In Bitcoin",
description: "",
unit: "US Dollars",
bottom: [
{
title: "Last",
@@ -31,8 +32,9 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerCoin,
name: "In Dollars",
title: "Last Coinbase (In Dollars)",
title: "Last Coinbase In Dollars",
description: "",
unit: "US Dollars",
bottom: [
{
title: "Last",
@@ -51,11 +53,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerMoneybag,
name: "In Bitcoin",
title: "Daily Sum Of Bitcoin Coinbases",
title: "Daily Sum Of Coinbases In Bitcoin",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Coinbases (Bitcoin)",
title: "Sum",
color: colors.bitcoin,
datasetPath: `/${scale}-to-coinbase`,
},
@@ -65,11 +68,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerCash,
name: "In Dollars",
title: "Daily Sum Of Dollar Coinbases",
title: "Daily Sum Of Coinbases In Dollars",
description: "",
unit: "US Dollars",
bottom: [
{
title: "Coinbases (Dollars)",
title: "Sum",
color: colors.dollars,
datasetPath: `/${scale}-to-coinbase-in-dollars`,
},
@@ -85,11 +89,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerMoneybag,
name: "In Bitcoin",
title: "Yearly Sum Of Bitcoin Coinbases",
title: "Yearly Sum Of Coinbases In Bitcoin",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Coinbases (Bitcoin)",
title: "Sum",
color: colors.bitcoin,
datasetPath: `/${scale}-to-coinbase-1y-sum`,
},
@@ -99,11 +104,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerCash,
name: "In Dollars",
title: "Yearly Sum Of Dollar Coinbases",
title: "Yearly Sum Of Coinbases In Dollars",
description: "",
unit: "US Dollars",
bottom: [
{
title: "Coinbases (Dollars)",
title: "Sum",
color: colors.dollars,
datasetPath: `/${scale}-to-coinbase-in-dollars-1y-sum`,
},
@@ -119,11 +125,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerMoneybag,
name: "In Bitcoin",
title: "Cumulative Bitcoin Coinbases",
title: "Cumulative Coinbases In Bitcoin",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Coinbases (Bitcoin)",
title: "Coinbases",
color: colors.bitcoin,
datasetPath: `/${scale}-to-cumulative-coinbase`,
},
@@ -133,11 +140,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerCash,
name: "In Dollars",
title: "Cumulative Dollar Coinbases",
title: "Cumulative Coinbases In Dollars",
description: "",
unit: "US Dollars",
bottom: [
{
title: "Coinbases (Dollars)",
title: "Coinbases",
color: colors.dollars,
datasetPath: `/${scale}-to-cumulative-coinbase-in-dollars`,
},
@@ -162,8 +170,9 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerCoinBitcoin,
name: "In Bitcoin",
title: "Last Subsidy (In Bitcoin)",
title: "Last Subsidy In Bitcoin",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Last",
@@ -176,8 +185,9 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerCoin,
name: "In Dollars",
title: "Last Subsidy (In Dollars)",
title: "Last Subsidy In Dollars",
description: "",
unit: "US Dollars",
bottom: [
{
title: "Last",
@@ -196,11 +206,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerMoneybag,
name: "In Bitcoin",
title: "Daily Sum Of Bitcoin Subsidies",
title: "Daily Sum Of Subsidies In Bitcoin",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Subsidies (Bitcoin)",
title: "Sum",
color: colors.bitcoin,
datasetPath: `/${scale}-to-subsidy`,
},
@@ -210,11 +221,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerCash,
name: "In Dollars",
title: "Daily Sum Of Dollar Subsidies",
title: "Daily Sum Of Subsidies In Dollars",
description: "",
unit: "US Dollars",
bottom: [
{
title: "Subsidies (Dollars)",
title: "Sum",
color: colors.dollars,
datasetPath: `/${scale}-to-subsidy-in-dollars`,
},
@@ -230,11 +242,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerMoneybag,
name: "In Bitcoin",
title: "Yearly Sum Of Bitcoin Subsidies",
title: "Yearly Sum Of Subsidies In Bitcoin",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Subsidies (Bitcoin)",
title: "Sum",
color: colors.bitcoin,
datasetPath: `/${scale}-to-subsidy-1y-sum`,
},
@@ -244,11 +257,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerCash,
name: "In Dollars",
title: "Yearly Sum Of Dollar Subsidies",
title: "Yearly Sum Of Subsidies In Dollars",
description: "",
unit: "US Dollars",
bottom: [
{
title: "Subsidies (Dollars)",
title: "Sum",
color: colors.dollars,
datasetPath: `/${scale}-to-subsidy-in-dollars-1y-sum`,
},
@@ -264,11 +278,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerMoneybag,
name: "In Bitcoin",
title: "Cumulative Bitcoin Subsidies",
title: "Cumulative Subsidies In Bitcoin",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Subsidies (Bitcoin)",
title: "Subsidies",
color: colors.bitcoin,
datasetPath: `/${scale}-to-cumulative-subsidy`,
},
@@ -278,11 +293,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerCash,
name: "In Dollars",
title: "Cumulative Dollar Subsidies",
title: "Cumulative Subsidies In Dollars",
description: "",
unit: "US Dollars",
bottom: [
{
title: "Subsidies (Dollars)",
title: "Subsidies",
color: colors.dollars,
datasetPath: `/${scale}-to-cumulative-subsidy-in-dollars`,
},
@@ -307,8 +323,9 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerCoinBitcoin,
name: "In Bitcoin",
title: "Last Fees (In Bitcoin)",
title: "Last Fees In Bitcoin",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Last",
@@ -321,8 +338,9 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerCoin,
name: "In Dollars",
title: "Last Fees (In Dollars)",
title: "Last Fees In Dollars",
description: "",
unit: "US Dollars",
bottom: [
{
title: "Last",
@@ -341,11 +359,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerMoneybag,
name: "In Bitcoin",
title: "Daily Sum Of Bitcoin Fees",
title: "Daily Sum Of Fees In Bitcoin",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Fees (Bitcoin)",
title: "Sum",
color: colors.bitcoin,
datasetPath: `/${scale}-to-fees`,
},
@@ -355,11 +374,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerCash,
name: "In Dollars",
title: "Daily Sum Of Dollar Fees",
title: "Daily Sum Of Fees In Dollars",
description: "",
unit: "US Dollars",
bottom: [
{
title: "Fees (Dollars)",
title: "Sum",
color: colors.dollars,
datasetPath: `/${scale}-to-fees-in-dollars`,
},
@@ -375,11 +395,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerMoneybag,
name: "In Bitcoin",
title: "Yearly Sum Of Bitcoin Fees",
title: "Yearly Sum Of Fees In Bitcoin",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Fees (Bitcoin)",
title: "Sum",
color: colors.bitcoin,
datasetPath: `/${scale}-to-fees-1y-sum`,
},
@@ -389,11 +410,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerCash,
name: "In Dollars",
title: "Yearly Sum Of Dollar Fees",
title: "Yearly Sum Of Fees In Dollars",
description: "",
unit: "US Dollars",
bottom: [
{
title: "Fees (Dollars)",
title: "Sum",
color: colors.dollars,
datasetPath: `/${scale}-to-fees-in-dollars-1y-sum`,
},
@@ -409,11 +431,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerMoneybag,
name: "In Bitcoin",
title: "Cumulative Bitcoin Fees",
title: "Cumulative Fees In Bitcoin",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Fees (Bitcoin)",
title: "Fees",
color: colors.bitcoin,
datasetPath: `/${scale}-to-cumulative-fees`,
},
@@ -423,11 +446,12 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerCash,
name: "In Dollars",
title: "Cumulative Dollar Fees",
title: "Cumulative Fees In Dollars",
description: "",
unit: "US Dollars",
bottom: [
{
title: "Fees (Dollars)",
title: "Fees",
color: colors.dollars,
datasetPath: `/${scale}-to-cumulative-fees-in-dollars`,
},
@@ -446,14 +470,15 @@ export function createPresets(scale: ResourceScale) {
name: "Subsidy V. Fees",
title: "Subsidy V. Fees",
description: "",
unit: "Percentage",
bottom: [
{
title: "Subsidy (%)",
title: "Subsidy",
color: colors.bitcoin,
datasetPath: `/${scale}-to-subsidy-to-coinbase-ratio`,
},
{
title: "Fees (%)",
title: "Fees",
color: colors.darkBitcoin,
datasetPath: `/${scale}-to-fees-to-coinbase-ratio`,
},
@@ -468,9 +493,7 @@ export function createPresets(scale: ResourceScale) {
name: "Puell Multiple",
title: "Puell Multiple",
description: "",
priceScaleOptions: {
mode: 1,
},
unit: "",
bottom: [
{
title: "Multiple",
@@ -484,11 +507,9 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerPick,
name: "Hash Rate",
title: "Hash Rate (EH/s)",
title: "Hash Rate",
description: "",
priceScaleOptions: {
mode: 1,
},
unit: "ExaHash / Second",
bottom: [
{
title: "1M SMA",
@@ -511,11 +532,9 @@ export function createPresets(scale: ResourceScale) {
scale,
icon: IconTablerRibbonHealth,
name: "Hash Ribbon",
title: "Hash Ribbon (EH/s)",
title: "Hash Ribbon",
description: "",
priceScaleOptions: {
mode: 1,
},
unit: "ExaHash / Second",
bottom: [
{
title: "1M SMA",
@@ -535,15 +554,16 @@ export function createPresets(scale: ResourceScale) {
name: "Hash Price",
title: "Hash Price",
description: "",
unit: "Dollars / (PetaHash / Second)",
bottom: [
{
title: "Price ($/PH/s)",
title: "Price",
color: colors.dollars,
datasetPath: `/date-to-hash-price`,
},
],
},
] satisfies PartialPreset[])
] as const satisfies PartialPreset[])
: []),
{
@@ -552,9 +572,7 @@ export function createPresets(scale: ResourceScale) {
name: "Difficulty",
title: "Difficulty",
description: "",
priceScaleOptions: {
mode: 1,
},
unit: "",
bottom: [
{
title: "Difficulty",
@@ -572,9 +590,10 @@ export function createPresets(scale: ResourceScale) {
name: "Difficulty Adjustment",
title: "Difficulty Adjustment",
description: "",
unit: "Percentage",
bottom: [
{
title: "Adjustment (%)",
title: "Adjustment",
seriesType: SeriesType.Based,
datasetPath: `/${scale}-to-difficulty-adjustment`,
},
@@ -589,9 +608,7 @@ export function createPresets(scale: ResourceScale) {
name: "Annualized Issuance",
title: "Annualized Issuance",
description: "",
priceScaleOptions: {
mode: 1,
},
unit: "Bitcoin",
bottom: [
{
title: "Issuance",
@@ -607,12 +624,10 @@ export function createPresets(scale: ResourceScale) {
name: "Yearly Inflation Rate",
title: "Yearly Inflation Rate",
description: "",
priceScaleOptions: {
mode: 1,
},
unit: "Percentage",
bottom: [
{
title: "Rate (%)",
title: "Rate",
color: colors.bitcoin,
datasetPath: `/${scale}-to-yearly-inflation-rate`,
},
@@ -21,6 +21,7 @@ export function createCohortPresetPricesPaidFolder({
name: `Average`,
title: `${title} Average Price Paid - Realized Price`,
description: "",
unit: "US Dollars",
icon: () => IconTablerMathAvg,
top: [
{
@@ -36,6 +37,7 @@ export function createCohortPresetPricesPaidFolder({
title: `${title} deciles`,
icon: () => IconTablerSquareHalf,
description: "",
unit: "US Dollars",
top: percentiles
.filter(({ value }) => Number(value) % 10 === 0)
.map(({ name, id }) => {
@@ -54,6 +56,7 @@ export function createCohortPresetPricesPaidFolder({
name: percentile.name,
title: `${title} ${percentile.title}`,
description: "",
unit: "US Dollars",
icon: () => IconTablerSquareHalf,
top: [
{
@@ -25,6 +25,7 @@ export function createCohortPresetRealizedFolder({
name: `Price`,
title: `${title} Realized Price`,
description: "",
unit: "US Dollars",
icon: () => IconTablerTag,
top: [
{
@@ -46,6 +47,7 @@ export function createCohortPresetRealizedFolder({
name: `Capitalization`,
title: `${title} Realized Capitalization`,
description: "",
unit: "US Dollars",
icon: () => IconTablerPigMoney,
bottom: [
{
@@ -70,6 +72,7 @@ export function createCohortPresetRealizedFolder({
name: `Capitalization 1M Net Change`,
title: `${title} Realized Capitalization 1 Month Net Change`,
description: "",
unit: "US Dollars",
icon: () => IconTablerStatusChange,
bottom: [
{
@@ -84,6 +87,7 @@ export function createCohortPresetRealizedFolder({
name: `Profit`,
title: `${title} Realized Profit`,
description: "",
unit: "US Dollars",
icon: () => IconTablerCash,
bottom: [
{
@@ -98,6 +102,7 @@ export function createCohortPresetRealizedFolder({
name: "Loss",
title: `${title} Realized Loss`,
description: "",
unit: "US Dollars",
icon: () => IconTablerCoffin,
bottom: [
{
@@ -112,6 +117,7 @@ export function createCohortPresetRealizedFolder({
name: `PNL`,
title: `${title} Realized Profit And Loss`,
description: "",
unit: "US Dollars",
icon: () => IconTablerArrowsVertical,
bottom: [
{
@@ -133,6 +139,7 @@ export function createCohortPresetRealizedFolder({
name: `Net PNL`,
title: `${title} Net Realized Profit And Loss`,
description: "",
unit: "US Dollars",
icon: () => IconTablerScale,
bottom: [
{
@@ -147,6 +154,7 @@ export function createCohortPresetRealizedFolder({
name: `Net PNL Relative To Market Cap`,
title: `${title} Net Realized Profit And Loss Relative To Market Capitalization`,
description: "",
unit: "Percentage",
icon: () => IconTablerDivide,
bottom: [
{
@@ -161,6 +169,7 @@ export function createCohortPresetRealizedFolder({
name: `Cumulative Profit`,
title: `${title} Cumulative Realized Profit`,
description: "",
unit: "US Dollars",
icon: () => IconTablerSum,
bottom: [
{
@@ -175,6 +184,7 @@ export function createCohortPresetRealizedFolder({
name: "Cumulative Loss",
title: `${title} Cumulative Realized Loss`,
description: "",
unit: "US Dollars",
icon: () => IconTablerSum,
bottom: [
{
@@ -189,6 +199,7 @@ export function createCohortPresetRealizedFolder({
name: `Cumulative Net PNL`,
title: `${title} Cumulative Net Realized Profit And Loss`,
description: "",
unit: "US Dollars",
icon: () => IconTablerSum,
bottom: [
{
@@ -203,6 +214,7 @@ export function createCohortPresetRealizedFolder({
name: `Cumulative Net PNL 30 Day Change`,
title: `${title} Cumulative Net Realized Profit And Loss 30 Day Change`,
description: "",
unit: "US Dollars",
icon: () => IconTablerTimeDuration30,
bottom: [
{
@@ -217,6 +229,7 @@ export function createCohortPresetRealizedFolder({
name: `Value Created`,
title: `${title} Value Created`,
description: "",
unit: "US Dollars",
icon: () => IconTablerPlus,
bottom: [
{
@@ -231,6 +244,7 @@ export function createCohortPresetRealizedFolder({
name: `Value Destroyed`,
title: `${title} Value Destroyed`,
description: "",
unit: "US Dollars",
icon: () => IconTablerMinus,
bottom: [
{
@@ -245,6 +259,7 @@ export function createCohortPresetRealizedFolder({
name: `Spent Output Profit Ratio - SOPR`,
title: `${title} Spent Output Profit Ratio`,
description: "",
unit: "Percentage",
icon: () => IconTablerMathXDivideY,
bottom: [
{
@@ -27,7 +27,7 @@ export function createCohortPresetSupplyFolder({
title: `${title} Profit And Loss`,
icon: () => IconTablerArrowsCross,
description: "",
unit: "US Dollars",
bottom: [
{
title: "In Profit",
@@ -60,6 +60,7 @@ export function createCohortPresetSupplyFolder({
title: `${title} Total supply`,
icon: () => IconTablerSum,
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Supply",
@@ -73,6 +74,7 @@ export function createCohortPresetSupplyFolder({
name: "In Profit",
title: `${title} Supply In Profit`,
description: "",
unit: "Bitcoin",
icon: () => IconTablerTrendingUp,
bottom: [
{
@@ -87,6 +89,7 @@ export function createCohortPresetSupplyFolder({
name: "In Loss",
title: `${title} Supply In Loss`,
description: "",
unit: "Bitcoin",
icon: () => IconTablerTrendingDown,
bottom: [
{
@@ -106,6 +109,7 @@ export function createCohortPresetSupplyFolder({
name: "All",
title: `${title} Profit And Loss Relative To Circulating Supply`,
description: "",
unit: "Percentage",
icon: () => IconTablerArrowsCross,
bottom: [
{
@@ -138,6 +142,7 @@ export function createCohortPresetSupplyFolder({
name: `Total`,
title: `${title} Total supply Relative To Circulating Supply`,
description: "",
unit: "Percentage",
icon: () => IconTablerSum,
bottom: [
{
@@ -152,6 +157,7 @@ export function createCohortPresetSupplyFolder({
name: "In Profit",
title: `${title} Supply In Profit Relative To Circulating Supply`,
description: "",
unit: "Percentage",
icon: () => IconTablerTrendingUp,
bottom: [
{
@@ -166,6 +172,7 @@ export function createCohortPresetSupplyFolder({
name: "In Loss",
title: `${title} Supply In Loss Relative To Circulating Supply`,
description: "",
unit: "Percentage",
icon: () => IconTablerTrendingDown,
bottom: [
{
@@ -185,6 +192,7 @@ export function createCohortPresetSupplyFolder({
name: "All",
title: `${title} Supply In Profit And Loss Relative To Own Supply`,
description: "",
unit: "Percentage",
icon: () => IconTablerArrowsCross,
bottom: [
{
@@ -221,6 +229,7 @@ export function createCohortPresetSupplyFolder({
name: "In Profit",
title: `${title} Supply In Profit Relative To Own Supply`,
description: "",
unit: "Percentage",
icon: () => IconTablerTrendingUp,
bottom: [
{
@@ -235,6 +244,7 @@ export function createCohortPresetSupplyFolder({
name: "In Loss",
title: `${title} Supply In Loss Relative To Own Supply`,
description: "",
unit: "Percentage",
icon: () => IconTablerTrendingDown,
bottom: [
{
@@ -24,6 +24,7 @@ export function createCohortPresetUnrealizedFolder({
name: `Profit`,
title: `${title} Unrealized Profit`,
description: "",
unit: "US Dollars",
icon: () => IconTablerMoodDollar,
bottom: [
{
@@ -38,6 +39,7 @@ export function createCohortPresetUnrealizedFolder({
name: "Loss",
title: `${title} Unrealized Loss`,
description: "",
unit: "US Dollars",
icon: () => IconTablerMoodSadDizzy,
bottom: [
{
@@ -52,6 +54,7 @@ export function createCohortPresetUnrealizedFolder({
name: `PNL`,
title: `${title} Unrealized Profit And Loss`,
description: "",
unit: "US Dollars",
icon: () => IconTablerArrowsVertical,
bottom: [
{
@@ -73,6 +76,7 @@ export function createCohortPresetUnrealizedFolder({
name: `Net PNL`,
title: `${title} Net Unrealized Profit And Loss`,
description: "",
unit: "US Dollars",
icon: () => IconTablerScale,
bottom: [
{
@@ -87,6 +91,7 @@ export function createCohortPresetUnrealizedFolder({
name: `Net PNL Relative To Market Cap`,
title: `${title} Net Unrealized Profit And Loss Relative To Total Market Capitalization`,
description: "",
unit: "Percentage",
icon: () => IconTablerDivide,
bottom: [
{
@@ -21,6 +21,7 @@ export function createCohortPresetUTXOFolder({
name: `Count`,
title: `${title} Unspent Transaction Outputs Count`,
description: "",
unit: "Count",
icon: () => IconTablerTicket,
bottom: [
{
+14 -7
View File
@@ -20,9 +20,10 @@ export function createRatioFolder({
{
scale,
name: "Basic",
description: "",
icon: IconTablerMathXDivideY,
title: `Market Price To ${title} Ratio`,
unit: "Ratio",
description: "",
top: [
{
title: `SMA`,
@@ -55,6 +56,7 @@ export function createRatioFolder({
name: "Averages",
description: "",
icon: IconTablerMathAvg,
unit: "Ratio",
title: `Market Price To ${title} Ratio Averages`,
top: [
{
@@ -98,9 +100,10 @@ export function createRatioFolder({
{
scale,
name: "Momentum Oscillator",
description: "",
icon: IconTablerWaveSine,
title: `Market Price To ${title} Ratio 1Y SMA Momentum Oscillator`,
description: "",
unit: "Ratio",
icon: IconTablerWaveSine,
top: [
{
title: `SMA`,
@@ -129,9 +132,10 @@ export function createRatioFolder({
{
scale,
name: "Top Percentiles",
description: "",
icon: IconTablerJetpack,
title: `Market Price To ${title} Ratio Top Percentiles`,
description: "",
unit: "Ratio",
top: [
{
title: `SMA`,
@@ -165,9 +169,10 @@ export function createRatioFolder({
{
scale,
name: "Bottom Percentiles",
description: "",
icon: IconTablerScubaMask,
title: `Market Price To ${title} Ratio Bottom Percentiles`,
description: "",
unit: "Ratio",
top: [
{
title: `SMA`,
@@ -201,9 +206,10 @@ export function createRatioFolder({
{
scale,
name: "Top Probabilities",
description: "",
icon: IconTablerRocket,
title: `${title} Top Probabilities`,
description: "",
unit: "US Dollars",
top: [
{
title: `0.1%`,
@@ -225,9 +231,10 @@ export function createRatioFolder({
{
scale,
name: "Bottom Probabilities",
description: "",
icon: IconTablerSubmarine,
title: `${title} Bottom Probabilities`,
description: "",
unit: "US Dollars",
top: [
{
title: `0.1%`,
@@ -1,5 +1,6 @@
export function createRecapPresets({
scale,
unit,
title,
keyAverage,
color,
@@ -15,6 +16,7 @@ export function createRecapPresets({
scale: ResourceScale;
title: string;
color: Color;
unit: Unit;
keySum?: AnyDatasetPath;
keyAverage?: AnyDatasetPath;
keyMax?: AnyDatasetPath;
@@ -34,6 +36,7 @@ export function createRecapPresets({
name: "Daily Sum",
title: `${title} Daily Sum`,
description: "",
unit,
bottom: [
{
title: "Sum",
@@ -52,6 +55,7 @@ export function createRecapPresets({
name: "Daily Average",
title: `${title} Daily Average`,
description: "",
unit,
bottom: [
{
title: "Average",
@@ -70,6 +74,7 @@ export function createRecapPresets({
name: "Daily Percentiles",
title: `${title} Daily Percentiles`,
description: "",
unit,
bottom: [
...(keyMax
? [
@@ -146,6 +151,7 @@ export function createRecapPresets({
name: "Daily Max",
title: `${title} Daily Max`,
description: "",
unit,
bottom: [
{
title: "Max",
@@ -164,6 +170,7 @@ export function createRecapPresets({
name: "Daily 90th Percentile",
title: `${title} Daily 90th Percentile`,
description: "",
unit,
bottom: [
{
title: "90%",
@@ -182,6 +189,7 @@ export function createRecapPresets({
name: "Daily 75th Percentile",
title: `${title} Size 75th Percentile`,
description: "",
unit,
bottom: [
{
title: "75%",
@@ -200,6 +208,7 @@ export function createRecapPresets({
name: "Daily Median",
title: `${title} Daily Median`,
description: "",
unit,
bottom: [
{
title: "Median",
@@ -218,6 +227,7 @@ export function createRecapPresets({
name: "Daily 25th Percentile",
title: `${title} Daily 25th Percentile`,
description: "",
unit,
bottom: [
{
title: "25%",
@@ -236,6 +246,7 @@ export function createRecapPresets({
name: "Daily 10th Percentile",
title: `${title} Daily 10th Percentile`,
description: "",
unit,
bottom: [
{
title: "10%",
@@ -254,6 +265,7 @@ export function createRecapPresets({
name: "Daily Min",
title: `${title} Daily Min`,
description: "",
unit,
bottom: [
{
title: "Min",
@@ -10,6 +10,7 @@ export function createPresets(scale: ResourceScale) {
name: "Count",
title: "Transaction Count",
description: "",
unit: "Count",
bottom: [
{
title: "1M SMA",
@@ -38,6 +39,7 @@ export function createPresets(scale: ResourceScale) {
name: "In Bitcoin",
title: "Transaction Volume",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "1M SMA",
@@ -62,9 +64,7 @@ export function createPresets(scale: ResourceScale) {
name: "In Dollars",
title: "Transaction Volume In Dollars",
description: "",
priceScaleOptions: {
mode: 1,
},
unit: "US Dollars",
bottom: [
{
title: "1M SMA",
@@ -95,6 +95,7 @@ export function createPresets(scale: ResourceScale) {
name: "In Bitcoin",
title: "Annualized Transaction Volume",
description: "",
unit: "Bitcoin",
bottom: [
{
title: "Volume",
@@ -109,6 +110,7 @@ export function createPresets(scale: ResourceScale) {
name: "In Dollars",
title: "Annualized Transaction Volume In Dollars",
description: "",
unit: "US Dollars",
bottom: [
{
title: "Volume",
@@ -125,6 +127,7 @@ export function createPresets(scale: ResourceScale) {
name: "Velocity",
title: "Transactions Velocity",
description: "",
unit: "",
bottom: [
{
title: "Transactions Velocity",
@@ -139,6 +142,7 @@ export function createPresets(scale: ResourceScale) {
name: "Per Second",
title: "Transactions Per Second",
description: "",
unit: "Transactions",
bottom: [
{
title: "1M SMA",
+18 -11
View File
@@ -1,13 +1,30 @@
interface PresetParams {
priceScaleOptions?: DeepPartialPriceScaleOptions;
top?: SeriesConfig[];
bottom?: SeriesConfig[];
}
type Unit =
| "US Dollars"
| "Bitcoin"
| "Percentage"
| "Height"
| "Count"
| "Megabytes"
| "Transactions"
| "Weight"
| "Ratio"
| "Virtual Bytes"
| "Seconds"
| "Coinblocks"
| "ExaHash / Second"
| "Dollars / (PetaHash / Second)"
| "";
type PartialPreset = {
scale: ResourceScale;
icon?: () => JSXElement;
name: string;
unit: Unit;
title: string;
description: string;
} & PresetParams;
@@ -24,16 +41,6 @@ type FilePath = {
name: string;
}[];
// type ApplyPreset = (params: {
// charts: RWS<IChartApi[]>;
// parentDiv: HTMLDivElement;
// datasets: Datasets;
// preset: Preset;
// legendSetter: Setter<SeriesLegend[]>;
// dark: Accessor<boolean>;
// activeIds: RWS<number[]>;
// }) => void;
interface PartialPresetFolder {
name: string;
tree: PartialPresetTree;
@@ -49,10 +49,10 @@ export const createStaticList = <T, L extends T[] = T[]>(
return found;
} else {
const index = savedIndex ?? parameters.selectedIndex;
const value = index !== undefined ? l.at(index) : undefined;
return (
l.at(savedIndex ?? parameters.selectedIndex!) ??
parameters.defaultValue ??
l[parameters.defaultIndex || 0]
value ?? parameters.defaultValue ?? l[parameters.defaultIndex || 0]
);
}
}),
+26
View File
@@ -0,0 +1,26 @@
import { requestIdleCallbackPossible } from "../env";
import { createRWS } from "./rws";
export function createWasIdleAccessor() {
const wasIdle = createRWS(false);
if (requestIdleCallbackPossible) {
const idleCallback = requestIdleCallback(() => {
wasIdle.set(true);
});
onCleanup(() => {
cancelIdleCallback(idleCallback);
});
} else {
const timeout = setTimeout(() => {
wasIdle.set(true);
}, 500);
onCleanup(() => {
clearTimeout(timeout);
});
}
return wasIdle as Accessor<boolean>;
}
+1
View File
@@ -4,6 +4,7 @@ type ISeriesApi<T extends SeriesType> =
import("lightweight-charts").ISeriesApi<T>;
type SeriesOptionsMap = import("lightweight-charts").SeriesOptionsMap;
type ISeriesApiAny = ISeriesApi<keyof SeriesOptionsMap>;
type ITimeScaleApi<Time> = import("lightweight-charts").ITimeScaleApi<Time>;
type IPriceLine = import("lightweight-charts").IPriceLine;
type ChartOptions = import("lightweight-charts").ChartOptions;
type DeepPartial<T> = import("lightweight-charts").DeepPartial<T>;
-22
View File
@@ -1,22 +0,0 @@
name: Rust
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose