Skip to content

Commit

Permalink
Add multiple series to the chart with proper styles
Browse files Browse the repository at this point in the history
  • Loading branch information
xbtmatt committed Mar 4, 2025
1 parent b7c8283 commit ca4649b
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 39 deletions.
22 changes: 17 additions & 5 deletions src/typescript/frontend/src/app/arena/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { redirect } from "next/navigation";
import { parseJSON } from "utils";
import { getCandlesticksRoute } from "../candlesticks/utils";
import { Period } from "@sdk/const";
import { ROUTES } from "router/routes";
import { symbolToEmojis } from "@sdk/emoji_data";

export const revalidate = 2;

Expand All @@ -20,11 +22,11 @@ export default async function Arena() {
"Could not get melee data. This probably means that the backend is running an outdated version of the processor" +
" without the arena processing. Please update."
);
redirect("/home");
redirect(ROUTES.home);
}

if (!arenaInfo) {
redirect("/home");
redirect(ROUTES.home);
}

const [market0, market1] = await Promise.all([
Expand All @@ -36,6 +38,11 @@ export default async function Arena() {
}),
]);

if (!market0 || !market1) {
console.warn("Couldn't fetch market state for one of the arena markets.");
redirect(ROUTES.home);
}

const to = Math.ceil(new Date().getTime() / 1000);
const countBack = 500;
const period = Period.Period1M;
Expand All @@ -48,13 +55,18 @@ export default async function Arena() {
parseJSON<PeriodicStateEventModel[]>(res)
),
]);
/* */

const [symbol0, symbol1] = [market0.market.symbolData.symbol, market1.market.symbolData.symbol];
const allSymbolEmojiData = [...symbolToEmojis(symbol0).emojis, ...symbolToEmojis(symbol1).emojis];

return (
<ArenaClient
allSymbolEmojiData={allSymbolEmojiData}
arenaInfo={arenaInfo}
market0={market0!}
market1={market1!}
market0={market0}
market1={market1}
symbol0={symbol0}
symbol1={symbol1}
candlesticksMarket0={candlesticksMarket0}
candlesticksMarket1={candlesticksMarket1}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const ChartContainer = (props: ChartContainerProps) => {
<MemoizedChart
symbol={props.symbol}
secondarySymbol={props.secondarySymbol}
className={props.className}
/>
</Suspense>
)}
Expand Down
20 changes: 16 additions & 4 deletions src/typescript/frontend/src/components/charts/PrivateChart.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useEffect, useRef } from "react";
import { MS_IN_ONE_DAY, WIDGET_OPTIONS } from "./const";
import { MS_IN_ONE_DAY, SECONDARY_SERIES_OVERRIDES, WIDGET_OPTIONS } from "./const";
import { type IChartingLibraryWidget, type Timezone, widget } from "@static/charting_library";
import { type ChartContainerProps } from "./types";
import { BrowserNotSupported } from "./BrowserNotSupported";
import { useDatafeed } from "./datafeed";
import { cn } from "lib/utils/class-name";

/**
* The TradingView Chart component. This component is responsible for rendering the TradingView chart with the usage of
Expand All @@ -17,7 +18,11 @@ import { useDatafeed } from "./datafeed";
* for a more detailed explanation of the architectural data flow.
* @returns
*/
export const Chart = ({ symbol, secondarySymbol = "" }: Omit<ChartContainerProps, "emojis">) => {
export const Chart = ({
symbol,
secondarySymbol = "",
className = "",
}: Omit<ChartContainerProps, "emojis">) => {
const tvWidget = useRef<IChartingLibraryWidget>();
const ref = useRef<HTMLDivElement>(null);
const datafeed = useDatafeed(symbol, secondarySymbol);
Expand Down Expand Up @@ -49,7 +54,13 @@ export const Chart = ({ symbol, secondarySymbol = "" }: Omit<ChartContainerProps
});

if (secondarySymbol) {
chart.createStudy("Overlay", true, false, { symbol: secondarySymbol });
chart.createStudy(
"Overlay",
true,
false,
{ symbol: secondarySymbol },
SECONDARY_SERIES_OVERRIDES
);
}
});
}
Expand All @@ -63,7 +74,8 @@ export const Chart = ({ symbol, secondarySymbol = "" }: Omit<ChartContainerProps
}, [datafeed, symbol, secondarySymbol]);

return (
<div className="relative w-full h-[420px]">
// Parent must be relative for the visual fallback with `BrowserNotSupported` to work.
<div className={cn("relative", className)}>
<BrowserNotSupported />
<div ref={ref} className="relative h-full w-full"></div>
</div>
Expand Down
19 changes: 19 additions & 0 deletions src/typescript/frontend/src/components/charts/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const WIDGET_OPTIONS: Omit<ChartingLibraryWidgetOptions, "datafeed" | "co
fullscreen: false,
autosize: true,
loading_screen: { backgroundColor: "#000000" },
compare_symbols: [],
overrides: {
"paneProperties.backgroundType": "solid",
"paneProperties.background": "#000000",
Expand Down Expand Up @@ -120,3 +121,21 @@ export const WIDGET_OPTIONS: Omit<ChartingLibraryWidgetOptions, "datafeed" | "co
},
],
};

export const SECONDARY_SERIES_OVERRIDES = {
style: 1, // Candlesticks.
"barStyle.upColor": GREEN,
"barStyle.downColor": PINK,
"candleStyle.upColor": GREEN,
"candleStyle.downColor": PINK,
"candleStyle.borderUpColor": GREEN,
"candleStyle.borderDownColor": PINK,
"candleStyle.wickUpColor": GREEN,
"candleStyle.wickDownColor": PINK,
"columnStyle.upColor": GREEN_OPACITY_HALF,
"columnStyle.downColor": PINK_OPACITY_HALF,
"hollowCandleStyle.upColor": GREEN,
"hollowCandleStyle.downColor": PINK,
"rangeStyle.upColor": GREEN,
"rangeStyle.downColor": PINK,
};
46 changes: 21 additions & 25 deletions src/typescript/frontend/src/components/charts/trading-view-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,31 +59,27 @@ export const searchSymbolsFromRegisteredMarketMap = ({
[]
);

export const constructLibrarySymbolInfo = (symbol: string): LibrarySymbolInfo => {
const emojis = getSymbolEmojisInString(symbol);
const marketAddress = getMarketAddress(emojis);
return {
ticker: symbol,
name: symbol,
description: marketAddress.toString(),
pricescale: 10 ** 9,
volume_precision: -Math.ceil(Math.log10(Number("0.00000100") * Number("100.00000000"))),
minmov: 1,
exchange: EXCHANGE_NAME,
listed_exchange: "",
session: "24x7",
// Note that `has_empty_bars` causes invalid `time order violation` errors if it's set to `true`.
// has_empty_bars: true,
has_seconds: false,
has_intraday: true,
has_daily: true,
has_weekly_and_monthly: false,
timezone: getClientTimezone() as Timezone,
type: "crypto",
supported_resolutions: CONFIGURATION_DATA.supported_resolutions,
format: "price",
};
};
export const constructLibrarySymbolInfo = (symbol: string): LibrarySymbolInfo => ({
ticker: symbol,
name: symbol,
description: symbol,
pricescale: 10 ** 9,
volume_precision: -Math.ceil(Math.log10(Number("0.00000100") * Number("100.00000000"))),
minmov: 1,
exchange: EXCHANGE_NAME,
listed_exchange: "",
session: "24x7",
// Note that `has_empty_bars` causes invalid `time order violation` errors if it's set to `true`.
// has_empty_bars: true,
has_seconds: false,
has_intraday: true,
has_daily: true,
has_weekly_and_monthly: false,
timezone: getClientTimezone() as Timezone,
type: "crypto",
supported_resolutions: CONFIGURATION_DATA.supported_resolutions,
format: "price",
});

export const symbolInfoToSymbol = (symbolInfo: LibrarySymbolInfo) => {
const ticker = symbolInfo.ticker;
Expand Down
2 changes: 2 additions & 0 deletions src/typescript/frontend/src/components/charts/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { type SymbolEmojiData } from "@sdk/emoji_data";
import { type ClassValue } from "clsx";

export type ChartContainerProps = {
symbol: string;
emojis: SymbolEmojiData[];
secondarySymbol?: string;
className?: ClassValue;
};
23 changes: 20 additions & 3 deletions src/typescript/frontend/src/components/pages/arena/ArenaClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import { createPortal } from "react-dom";
import { parseJSON } from "utils";
import { Box, EmojiTitle, type ArenaPropsWithPositionAndHistory, type ArenaProps } from "./utils";
import { BottomNavigation, TabContainer } from "./tabs";
import { PriceChartDesktopBox, PriceChartMobile } from "./PriceChart";
import { useAptos } from "context/wallet-context/AptosContextProvider";
import {
type ArenaLeaderboardHistoryWithArenaInfoModel,
type ArenaPositionModel,
} from "@sdk/indexer-v2/types";
import { ROUTES } from "router/routes";
import ChartContainer from "@/components/charts/ChartContainer";
import { type ClassValue } from "clsx";

const RewardsRemainingBox = ({ rewardsRemaining }: { rewardsRemaining: bigint }) => {
const { isMobile } = useMatchBreakpoints();
Expand All @@ -34,6 +35,8 @@ const RewardsRemainingBox = ({ rewardsRemaining }: { rewardsRemaining: bigint })
);
};

const chartBoxClassName: ClassValue = "relative w-full h-full col-start-1 col-end-3";

const Desktop = (props: ArenaPropsWithPositionAndHistory) => {
const { arenaInfo, market0, market1 } = props;
return (
Expand All @@ -54,7 +57,14 @@ const Desktop = (props: ArenaPropsWithPositionAndHistory) => {
<Countdown startTime={arenaInfo.startTime} duration={arenaInfo.duration / 1000n / 1000n} />
</Box>
<RewardsRemainingBox rewardsRemaining={arenaInfo.rewardsRemaining} />
<PriceChartDesktopBox {...props} />
<Box className={chartBoxClassName}>
<ChartContainer
emojis={props.allSymbolEmojiData}
symbol={props.symbol0}
secondarySymbol={props.symbol1}
className="w-full h-full"
/>
</Box>
<Box className="col-start-3 col-end-5 h-[100%]">
<TabContainer {...props} />
</Box>
Expand All @@ -81,7 +91,14 @@ const Mobile = (props: ArenaPropsWithPositionAndHistory) => {
</Box>
<RewardsRemainingBox rewardsRemaining={arenaInfo.rewardsRemaining} />
<Box className="h-[500px]">
<PriceChartMobile {...props} />
<Box className={chartBoxClassName}>
<ChartContainer
emojis={props.allSymbolEmojiData}
symbol={props.symbol0}
secondarySymbol={props.symbol1}
className="w-full h-full"
/>
</Box>
</Box>
</div>
{createPortal(<BottomNavigation {...props} />, document.body)}
Expand Down
4 changes: 4 additions & 0 deletions src/typescript/frontend/src/components/pages/arena/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useMatchBreakpoints } from "@hooks/index";
import { type SymbolEmojiData } from "@sdk/emoji_data";
import {
type ArenaLeaderboardHistoryWithArenaInfoModel,
type ArenaPositionModel,
Expand All @@ -12,9 +13,12 @@ import darkTheme from "theme/dark";
import { GlowingEmoji } from "utils/emoji";

export type ArenaProps = {
allSymbolEmojiData: SymbolEmojiData[];
arenaInfo: ArenaInfoModel;
market0: MarketStateModel;
market1: MarketStateModel;
symbol0: string;
symbol1: string;
candlesticksMarket0: PeriodicStateEventModel[];
candlesticksMarket1: PeriodicStateEventModel[];
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ const DesktopGrid = (props: GridProps) => {
<StyledBlock width="57%" className="bg-black z-10">
<StyledBlockWrapper>
<Suspense fallback={<Loading numEmojis={20} />}>
<ChartContainer symbol={props.data.symbolData.symbol} emojis={props.data.emojis} />
<ChartContainer
symbol={props.data.symbolData.symbol}
emojis={props.data.emojis}
className="relative w-full h-[420px]"
/>
</Suspense>
</StyledBlockWrapper>
</StyledBlock>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ const MobileGrid = (props: GridProps) => {
{DISPLAY_HEADER_ABOVE_CHART && <StyledMobileContentHeader></StyledMobileContentHeader>}
<StyledMobileContentInner className={HEIGHT}>
<Suspense fallback={<Loading />}>
<ChartContainer symbol={props.data.symbol} emojis={props.data.emojis} />
<ChartContainer
symbol={props.data.symbol}
emojis={props.data.emojis}
className="relative w-full h-[420px]"
/>
</Suspense>
</StyledMobileContentInner>
</StyledMobileContentBlock>
Expand Down

0 comments on commit ca4649b

Please sign in to comment.