Skip to content

Commit

Permalink
feat: add price axis to charts (#3304)
Browse files Browse the repository at this point in the history
* feat: ✨ improve chart rendering

* feat: ✨ add chart right price scale

* feat: 💄 improve char tooltip format

* feat: ✨ improve price axis formatting

* feat: ✨ add horizontal line for crosshair

* feat: ✨ remove crosshair labels from chart

* feat: ✨ add hover date belove chart header price

* feat: ✨ display hover date only on actual hover

* fix: 🐛 fix utc date formatting
  • Loading branch information
DavideSegullo committed Jun 7, 2024
1 parent f8461a1 commit 2ca9996
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 71 deletions.
69 changes: 32 additions & 37 deletions packages/web/components/chart/light-weight-charts/chart.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
ColorType,
DeepPartial,
isBusinessDay,
LineStyle,
MouseEventParams,
TickMarkType,
Expand All @@ -17,6 +16,10 @@ import React, {
useSyncExternalStore,
} from "react";

import {
priceFormatter,
timepointToString,
} from "~/components/chart/light-weight-charts/utils";
import { theme } from "~/tailwind.config";

import {
Expand All @@ -33,38 +36,6 @@ function resizeSubscribe(callback: (this: Window, ev: UIEvent) => unknown) {
};
}

const timepointToString = (
timePoint: Time,
formatOptions: Intl.DateTimeFormatOptions,
locale?: string
) => {
let date = new Date();

if (typeof timePoint === "string") {
date = new Date(timePoint);
} else if (!isBusinessDay(timePoint)) {
date = new Date((timePoint as number) * 1000);
} else {
date = new Date(
Date.UTC(timePoint.year, timePoint.month - 1, timePoint.day)
);
}

// from given date we should use only as UTC date or timestamp
// but to format as locale date we can convert UTC date to local date
const localDateFromUtc = new Date(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate(),
date.getUTCHours(),
date.getUTCMinutes(),
date.getUTCSeconds(),
date.getUTCMilliseconds()
);

return localDateFromUtc.toLocaleString(locale, formatOptions);
};

export const defaultOptions: DeepPartial<TimeChartOptions> = {
layout: {
fontFamily: theme.fontFamily.subtitle1.join(","),
Expand All @@ -76,15 +47,38 @@ export const defaultOptions: DeepPartial<TimeChartOptions> = {
fontSize: 14,
},
grid: { horzLines: { visible: false }, vertLines: { visible: false } },
rightPriceScale: { visible: false },
leftPriceScale: { visible: false },
rightPriceScale: {
autoScale: true,
borderVisible: false,
ticksVisible: false,
scaleMargins: {
top: 0.25,
bottom: 0,
},
},
leftPriceScale: {
autoScale: true,
borderVisible: false,
ticksVisible: false,
scaleMargins: {
top: 0.25,
bottom: 0,
},
},
crosshair: {
horzLine: { visible: false },
horzLine: {
labelBackgroundColor: theme.colors.osmoverse[850],
style: LineStyle.LargeDashed,
width: 2,
color: `${theme.colors.osmoverse[300]}33`,
labelVisible: false,
},
vertLine: {
labelBackgroundColor: theme.colors.osmoverse[850],
style: LineStyle.LargeDashed,
width: 2,
color: `${theme.colors.osmoverse[300]}33`,
labelVisible: false,
},
},
handleScroll: false,
Expand All @@ -94,6 +88,7 @@ export const defaultOptions: DeepPartial<TimeChartOptions> = {
mouse: false,
},
localization: {
priceFormatter,
timeFormatter: (timePoint: Time) => {
const formatOptions: Intl.DateTimeFormatOptions = {
year: "numeric",
Expand Down Expand Up @@ -206,7 +201,7 @@ export const Chart = memo(
}, []);

return (
<div className="relative h-full" ref={setContainer}>
<div className="relative h-full [&_table]:table-auto" ref={setContainer}>
{children}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ export class LinearChartController extends AreaChartController {

return `
<h6 class="text-h6 font-semibold text-white-full whitespace-nowrap">
$
${
formatPretty(closeDec, {
maxDecimals,
currency: "USD",
style: "currency",
...formatOpts,
}) || ""
}
Expand Down
41 changes: 41 additions & 0 deletions packages/web/components/chart/light-weight-charts/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Dec } from "@keplr-wallet/unit";
import { isBusinessDay, Time } from "lightweight-charts";

import { formatPretty, getPriceExtendedFormatOptions } from "~/utils/formatter";
import { getDecimalCount } from "~/utils/number";

export const priceFormatter = (price: number) => {
const minimumDecimals = 2;
const maxDecimals = Math.max(getDecimalCount(price), minimumDecimals);

const priceDec = new Dec(price);

const formatOpts = getPriceExtendedFormatOptions(priceDec);

return formatPretty(priceDec, {
maxDecimals,
currency: "USD",
style: "currency",
...formatOpts,
});
};

export const timepointToString = (
timePoint: Time,
formatOptions: Intl.DateTimeFormatOptions,
locale?: string
) => {
let date = new Date();

if (typeof timePoint === "string") {
date = new Date(timePoint);
} else if (!isBusinessDay(timePoint)) {
date = new Date((timePoint as number) * 1000);
} else {
date = new Date(
Date.UTC(timePoint.year, timePoint.month - 1, timePoint.day)
);
}

return date.toLocaleString(locale, formatOptions);
};
24 changes: 16 additions & 8 deletions packages/web/components/chart/price-historical-v2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,40 @@ import {
AreaData,
AreaSeriesOptions,
DeepPartial,
Time,
UTCTimestamp,
} from "lightweight-charts";
import React, { FunctionComponent, memo } from "react";

import { LinearChartController } from "~/components/chart/light-weight-charts/linear-chart";
import { AreaChartController } from "~/components/chart/light-weight-charts/area-chart";
import { theme } from "~/tailwind.config";

import { Chart } from "./light-weight-charts/chart";

const seriesOpt: DeepPartial<AreaSeriesOptions> = {
lineColor: "#8C8AF9",
topColor: "rgba(60, 53, 109, 1)",
bottomColor: "rgba(32, 27, 67, 1)",
lineColor: theme.colors.wosmongton[300],
topColor: theme.colors.osmoverse[700],
bottomColor: theme.colors.osmoverse[850],
priceLineVisible: false,
priceScaleId: "left",
lastValueVisible: false,
priceScaleId: "right",
crosshairMarkerBorderWidth: 0,
crosshairMarkerRadius: 8,
priceFormat: {
type: "price",
precision: 10,
minMove: 0.0000001,
},
};

export const HistoricalPriceChartV2: FunctionComponent<{
data: { close: number; time: number }[];
onPointerHover?: (price: number) => void;
onPointerHover?: (price: number, time: Time) => void;
onPointerOut?: () => void;
}> = memo(({ data = [], onPointerHover, onPointerOut }) => {
return (
<Chart
Controller={LinearChartController}
Controller={AreaChartController}
series={[
{
type: "Area",
Expand All @@ -42,7 +50,7 @@ export const HistoricalPriceChartV2: FunctionComponent<{
if (params.seriesData.size > 0) {
const [data] = [...params.seriesData.values()] as AreaData[];

onPointerHover?.(data.value);
onPointerHover?.(data.value, data.time);
} else {
onPointerOut?.();
}
Expand Down
64 changes: 41 additions & 23 deletions packages/web/components/chart/price-historical.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ export const PriceChartHeader: FunctionComponent<{
historicalRange: PriceRange;
setHistoricalRange: (pr: PriceRange) => void;
hoverPrice: number;
hoverDate?: string | null;
decimal: number;
formatOpts?: FormatOptions;
fiatSymbol?: string;
Expand All @@ -272,6 +273,7 @@ export const PriceChartHeader: FunctionComponent<{
setHistoricalRange,
baseDenom,
quoteDenom,
hoverDate,
hoverPrice,
formatOpts,
decimal,
Expand Down Expand Up @@ -318,29 +320,45 @@ export const PriceChartHeader: FunctionComponent<{
classes?.pricesHeaderContainerClass
)}
>
<SkeletonLoader isLoaded={!isLoading}>
<h4
className={classNames(
"row-span-2 pr-1 font-caption sm:text-h5",
classes?.priceHeaderClass
)}
>
{fiatSymbol}
{compactZeros ? (
<>
{significantDigits}.
{Boolean(zeros) && (
<>
0<sub title={`${getFormattedPrice()}USD`}>{zeros}</sub>
</>
)}
{decimalDigits}
</>
) : (
getFormattedPrice()
)}
</h4>
</SkeletonLoader>
<div>
<SkeletonLoader isLoaded={!isLoading}>
<h4
className={classNames(
"row-span-2 pr-1 font-caption sm:text-h5",
classes?.priceHeaderClass
)}
>
{fiatSymbol}
{compactZeros ? (
<>
{significantDigits}.
{Boolean(zeros) && (
<>
0<sub title={`${getFormattedPrice()}USD`}>{zeros}</sub>
</>
)}
{decimalDigits}
</>
) : (
getFormattedPrice()
)}
</h4>
</SkeletonLoader>
{hoverDate !== undefined ? (
<p
className={classNames(
"flex flex-1 flex-col justify-center font-caption text-wosmongton-200",
{
"invisible h-6": hoverDate === null,
}
)}
>
{hoverDate}
</p>
) : (
false
)}
</div>
{baseDenom && quoteDenom ? (
<div
className={classNames(
Expand Down
26 changes: 25 additions & 1 deletion packages/web/hooks/ui-config/use-asset-info-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import {
type TokenHistoricalPrice,
} from "@osmosis-labs/server";
import dayjs from "dayjs";
import { Time } from "lightweight-charts";
import { action, computed, makeObservable, observable } from "mobx";
import { useEffect, useMemo } from "react";

import { timepointToString } from "~/components/chart/light-weight-charts/utils";
import { api } from "~/utils/trpc";

export const useAssetInfoConfig = (
Expand Down Expand Up @@ -205,6 +207,9 @@ export class ObservableAssetInfoConfig {
@observable
protected _hoverPrice: number = 0;

@observable
protected _hoverDate?: Time = undefined;

@observable
protected _historicalData: TokenHistoricalPrice[] = [];

Expand Down Expand Up @@ -275,9 +280,27 @@ export class ObservableAssetInfoConfig {
if (!fiat) {
return undefined;
}

return new PricePretty(fiat, this._hoverPrice);
}

@computed
get hoverDate(): string | null {
if (!this._hoverDate) {
return null;
}

const formatOptions: Intl.DateTimeFormatOptions = {
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "numeric",
};

return timepointToString(this._hoverDate, formatOptions, "en-US");
}

@computed
get lastChartPrice(): ChartTick | undefined {
const prices: ChartTick[] = [...this.historicalChartData];
Expand Down Expand Up @@ -307,8 +330,9 @@ export class ObservableAssetInfoConfig {
};

@action
readonly setHoverPrice = (price: number) => {
readonly setHoverPrice = (price: number, time?: Time) => {
this._hoverPrice = price;
this._hoverDate = time;
};

@action
Expand Down
3 changes: 2 additions & 1 deletion packages/web/pages/assets/[denom].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ const TokenChartHeader = observer(() => {
decimal={maxDecimals}
showAllRange
hoverPrice={hoverPrice}
hoverDate={assetInfoConfig.hoverDate}
historicalRange={assetInfoConfig.historicalRange}
setHistoricalRange={assetInfoConfig.setHistoricalRange}
fiatSymbol={fiatSymbol}
Expand Down Expand Up @@ -503,7 +504,7 @@ const TokenChart = observer(() => {
data={assetInfoConfig.historicalChartData}
onPointerHover={assetInfoConfig.setHoverPrice}
onPointerOut={() => {
assetInfoConfig.setHoverPrice(0);
assetInfoConfig.setHoverPrice(0, undefined);
}}
/>
</>
Expand Down

0 comments on commit 2ca9996

Please sign in to comment.