Frontend developer from Merkeleon shares his experience summary in this TradingView tutorial
TradingView library is a complex mechanism with several ways to use it. To be the leading library for displaying stock transactions, it is worth providing a solid base for integration with other solutions. In my opinion, such integration includes:
React frontend developers often choose among available solutions on the Internet: which one is the most suitable. If I had such a choice, the TradingView library or something else, I would land the former anyway. TradingView documentation describes how to use the library in detail. It indicates developers’ responsible approach, since even programmers who are far from developing the library can cope with the installation.
It is worth noting that before I had not used simpler chart integration solutions, say through TradingView widgets. My acquaintance with the product began right from the main charting library. In this TradingView guide, I will explain how it was.
TradingView library had already been connected by my colleagues before I started to work on this project. I investigated the product in the process. My path of learning and supporting TradingView integration consisted of the following main iterations:
Supporting the already integrated version of the library was easy. I followed the main rule in the dev community: if it works, don’t touch it :). This approach sometimes failed, and I had to handle those tasks. One of the challenges I faced when working with TradingView was finding a solution to recolorize the chart itself.
When implementing software, it is extremely important to take into account customer’s branding and corporate colors. Seems like a trifle. Nevertheless, to fix this problem, it is necessary to understand the API provided for configuring the color scheme of the TradingView chart. Then find a suitable key responsible for the desired UI component in their library, that enables to change the color.
At that time, I didn’t understand how the data library works. I didn’t know what UDF-endpoints were. I wasn’t aware that part of the rendering data lies with the backend team. That there was a separate project that allows making changes both to the mechanism and to work with UDF-endpoints data. These had to be sorted out.
With the transition to a new design and the frontend update, it became pointless to use the previous version of the library in 2022. Therefore, we updated TradingView to a new version (April 2022). By the way, the library developers promptly reported on updates and all the improvements in TradingView blog.
During the update, I was pleased with the license and the multi-level access system to the GitHub repository, which was already available in Merkeleon at that time. It indicated the right approach to controlling and restricting access in one of the most recognizable interface elements in the trading community.
I saw drastic changes in the repository. I had to delve into how to use TradingView library and started to update the library relying on a previous example. 90 percent of the task was completed. Due to several outdated blocks, I had to dive deeper into reading the documentation than I initially assumed. Admittedly, the study of the described functionality consumed a lot of time. But I didn’t expect updating the library to be like a breeze. Thanks to the TradingView developers for great examples. They showed how to integrate the library on different platforms, including React. It facilitated the work and helped with the migration of the previous code.
After launching the TradingView library, there were two serious challenges: to tackle the issue of branding (recolorization) and to figure out why the chart would not be drawn based on the data.
To solve the problem of recolorization, I needed a mechanism that would allow customizing the original color scheme. After all the updates, the original color scheme was divided into 2 directions: the field for the chart, that is, what the user can change, and the frame of the graph, in other words, a panel with buttons.
For the first case, there is an overrides property that helps reassign chart variables if necessary. However, my task was not to constantly use the same set of colors, but to make the following 2 important options available in addition to the basic branding of the exchange:
Thoroughly studying how TradingView chart works, and realizing that the user-configured data must be stored somewhere in the library, I visited LocalStorage. There I invented an idea that was further implemented. During the first log in, TradingView is painted in brand colors. When the user changes colors, they no longer apply to the design. Switching the theme from light to dark is also unavailable. At the same time, if the operator wants, user settings can be reset after rebranding.
After understanding the structure of data from LocalStorage and finding patterns between the names of properties in LocalStorage and in the overrides properties of the graph, I developed a plan. To work with styles, the Merkeleon team decided to use styled-components. We had full access to the color variables from the JS side. The functionality of the hooks of modern React also helped. Yet, through a very complex algorithm of working with data and updating them. Still optimized enough, without extra performance problems. It is worth saying thanks to the TradingView developers: they reacted to the changes in LocalStorage and use the priority data from it :).
Next was the work with CSS. Due to the use of CSS generation, on our side the JS challenge became very interesting and had a few tricks. 🙂 One of the issues that did not allow CSS variables to be pushed through styled-components was that the library itself renders <iframe> inside the main <div> in the DOM. Therefore, it was necessary to figure out how to put the prepared data into <iframe> — from the CSS side there is no way to interfere with the work of <iframe>, but from the JS side… The preparation of data itself was not difficult. To solve the task, I had to use head.insertAdjacentElement in <iframe>. You cannot do without workaround in this job :).
It worked. But there was another concern on our side. TradingView has its own mechanism for loading the chart, which renders the preloader, and then the <iframe>. <iframe> is subject to CSS variables. Because of this, there were delays in the application of colors on our side: the standard TradingView palette was used first, then ours was applied. The callbacks provided by TradingView helped to cope with this problem. What we did: before executing onChartReady, we showed our preloader rendered on top of the chart. When the chart was prepared on the library side, we removed our preloader. By this time, our prepared CSS data had been delivered to <iframe>, and the library had time to respond.
Next, let’s figure out why the chart would not be drawn on the basis of data. I had to work with UDF-endpoints, or rather with a separate project in GitHub next to the library (link to private repository, hereinafter referred to as JS UDF). In JS UDF, it was necessary to add importing of the necessary headers in the header of the HTTP request from our side. Safe import should be triggered every time the chart calls our endpoints.
It took me a while to realize that there was another project with the source code. Instead, I found the already compiled code for JS UDF in the same repository. Then I opened the unified JS, found there a mention of fetch and the headers forwarding mechanism. It was necessary to understand which of the variables to change at the time of code execution. It was difficult to do so in the unified code. But it worked. I was pleased with the library again: the data structure did not change. I updated the library to the latest version (March 2022). The final integration for our needs is presented below.
import React, {useEffect, useCallback, useContext, useRef, useMemo, useState} from "react"; import styled from "styled-components"; import {createItemCache} from ""; // data caching function (Merkeleon) import {useIsTablet} from ""; // hook that tells the Tablet whether the device is relative to the size of the site's display area (Merkeleon) import {loadScript} from ""; // script loading function (Merkeleon) import Preloader from ""; // preloader component (Merkeleon) import {ThemeContext, themeDarkValue} from ""; // a set of imports for working with themes (Merkeleon) import {sort} from ""; // sort function (Merkeleon) import {widget} from "libraries/TradingView/charting_library/charting_library.esm.js"; import type {ThemeType} from ""; // typing for data with topics (Merkeleon) import type {CustomizationDesignColorsType} from ""; // typing color variables (Merkeleon) import type { IChartingLibraryWidget, LanguageCode as TradingViewLanguageCode, ChartingLibraryWidgetOptions, Overrides, ResolutionString, } from "TradingView/charting_library/charting_library.d"; type AvailableLanguageType = TradingViewLanguageCode; type ComponentPropsType = { availableLocale: AvailableLanguageType; selectedCurrencyPair: string | undefined; }; // start: Magic with colors const defaultCSSVariablesMetadata: { [tradingViewColorVariable: string]: keyof CustomizationDesignColorsType; } = { "--tv-color-platform-background": "color1", "--tv-color-pane-background": "color2", "--tv-color-pane-background-secondary": "color3", "--tv-color-toolbar-button-background-hover": "color4", "--tv-color-toolbar-button-background-secondary-hover": "color5", "--tv-color-toolbar-button-text": "color6", "--tv-color-toolbar-button-text-hover": "color7", "--tv-color-toolbar-button-text-active": "color8", "--tv-color-toolbar-button-text-active-hover": "color9", "--tv-color-item-active-text": "color8", "--tv-color-toolbar-toggle-button-background-active": "color10", "--tv-color-toolbar-toggle-button-background-active-hover": "color5", }; const defaultSettingsOverridesMetadata: { [tradingViewColorVariable: string]: keyof CustomizationDesignColorsType; } = { "paneProperties.background": "color2", "paneProperties.vertGridProperties.color": "color2", "paneProperties.horzGridProperties.color": "color2", "scalesProperties.textColor": "color6", "paneProperties.crossHairProperties.color": "color6", "mainSeriesProperties.candleStyle.upColor": "color11", "mainSeriesProperties.candleStyle.downColor": "color12", "mainSeriesProperties.candleStyle.borderColor": "color11", "mainSeriesProperties.candleStyle.borderUpColor": "color11", "mainSeriesProperties.candleStyle.borderDownColor": "color12", "mainSeriesProperties.hollowCandleStyle.upColor": "color11", "mainSeriesProperties.hollowCandleStyle.downColor": "color12", "mainSeriesProperties.hollowCandleStyle.borderColor": "color11", "mainSeriesProperties.hollowCandleStyle.borderUpColor": "color11", "mainSeriesProperties.hollowCandleStyle.borderDownColor": "color12", "mainSeriesProperties.haStyle.upColor": "color11", "mainSeriesProperties.haStyle.downColor": "color12", "mainSeriesProperties.haStyle.borderColor": "color11", "mainSeriesProperties.haStyle.borderUpColor": "color11", "mainSeriesProperties.haStyle.borderDownColor": "color12", "mainSeriesProperties.barStyle.upColor": "color11", "mainSeriesProperties.barStyle.downColor": "color12", "mainSeriesProperties.lineStyle.color": "color11", "mainSeriesProperties.areaStyle.color1": "color11", "mainSeriesProperties.areaStyle.color2": "color12", "mainSeriesProperties.areaStyle.linecolor": "color11", }; const getColorThemeByTheme = ( value: { [tradingViewColorVariable: string]: keyof CustomizationDesignColorsType; }, theme: ThemeType | undefined ): {[tradingViewColorVariable: string]: string} | undefined => { if (!theme) return; return Object.keys(value).reduce( (acc: {[tradingViewColorVariable: string]: string}, tradingViewColorVariable) => { const tradingViewColorValue = value[tradingViewColorVariable]; const colorValue = theme[tradingViewColorValue]; if (!colorValue) return acc; acc[tradingViewColorVariable] = colorValue; return acc; }, {} ); }; const tradingViewCustomizationColorLocalStorageKey = "Merkeleon.LocalStorageKey1"; const useNeedResetTradingViewLocalStorageCache = () => { const {themeDark, themeLight} = useContext(ThemeContext) || {}; const customizationColorsCacheJSON = useMemo(() => { if (!themeDark || !themeLight) return; const customizationColorKeySet: Set<keyof CustomizationDesignColorsType> = new Set([ ...Object.values(defaultCSSVariablesMetadata), ...Object.values(defaultSettingsOverridesMetadata), ]); const customizationColorKeyUniqArr = [...customizationColorKeySet]; sort(customizationColorKeyUniqArr); const customizationColorsCache = customizationColorKeyUniqArr.reduce( (acc: {[colorKey: string]: [string, string]}, colorKey) => { const darkValue = themeDark[colorKey]; const lightValue = themeLight[colorKey]; if (!darkValue || !lightValue) return acc; acc[colorKey] = [lightValue, darkValue]; return acc; }, {} ); return JSON.stringify(customizationColorsCache); }, [themeDark, themeLight]); const tradingViewChartPropertiesJSON = window.localStorage.getItem( tradingViewCustomizationColorLocalStorageKey ); if ( typeof customizationColorsCacheJSON !== "undefined" && customizationColorsCacheJSON !== tradingViewChartPropertiesJSON ) { window.localStorage.setItem( tradingViewCustomizationColorLocalStorageKey, customizationColorsCacheJSON ); return true; } return false; }; const useTradingViewCSSVariables = () => { const {themeDark, themeLight, currentTheme} = useContext(ThemeContext) || {}; const cssVariablesDark: | { [tradingViewColorVariable: string]: string; } | undefined = useMemo( () => getColorThemeByTheme(defaultCSSVariablesMetadata, themeDark), [themeDark] ); const cssVariablesLight: | { [tradingViewColorVariable: string]: string; } | undefined = useMemo( () => getColorThemeByTheme(defaultCSSVariablesMetadata, themeLight), [themeLight] ); const cssVariables = currentTheme === themeDarkValue ? cssVariablesDark : cssVariablesLight; if (!cssVariables) return; return Object.keys(cssVariables).reduce((acc, CSSVariableKey) => { acc += `${CSSVariableKey}: ${cssVariables[CSSVariableKey]};`; return acc; }, ""); }; const tradingViewChartPropertiesLocalStorageKey = "tradingview.chartproperties"; const useTradingViewOverrides = () => { const {themeDark, themeLight, currentTheme} = useContext(ThemeContext) || {}; const needResetTradingViewLocalStorageCache = useNeedResetTradingViewLocalStorageCache(); const mutateSettingOverrides = useCallback((currentOverrides: Overrides | undefined) => { if (!currentOverrides) return; const tradingViewChartPropertiesJSON = window.localStorage.getItem( tradingViewChartPropertiesLocalStorageKey ); try { const tradingViewChartProperties = JSON.parse(tradingViewChartPropertiesJSON || ""); Object.keys(currentOverrides).forEach(overridesKey => { const overridesPath = overridesKey.split("."); let currentValue = tradingViewChartProperties[overridesPath[0]]; if (currentValue) { let depth = 1; while (overridesPath.length !== depth && typeof currentValue !== "string") { const layerValue = currentValue[overridesPath[depth]]; if (!layerValue) break; currentValue = layerValue; depth++; } } if (typeof currentValue === "string") currentOverrides[overridesKey] = currentValue; }); } catch { window.localStorage.removeItem(tradingViewChartPropertiesLocalStorageKey); } }, []); const upgradeTradingViewChartPropertiesLocalStorage = useCallback( (currentOverrides: Overrides | undefined) => { if (!currentOverrides) return; const tradingViewChartPropertiesJSON = window.localStorage.getItem( tradingViewChartPropertiesLocalStorageKey ); try { const tradingViewChartProperties = JSON.parse(tradingViewChartPropertiesJSON || ""); Object.keys(currentOverrides).forEach(overridesKey => { const overridesPath = overridesKey.split("."); let currentLayer = tradingViewChartProperties[overridesPath[0]]; if (currentLayer) { let depth = 1; while (overridesPath.length - 1 !== depth) { const layerValue = currentLayer[overridesPath[depth]]; if (!layerValue) break; currentLayer = layerValue; depth++; } if (typeof currentLayer === "object") currentLayer[overridesPath[depth]] = currentOverrides[overridesKey]; } }); const tradingViewChartPropertiesJSONNew = JSON.stringify(tradingViewChartProperties); window.localStorage.setItem( tradingViewChartPropertiesLocalStorageKey, tradingViewChartPropertiesJSONNew ); } catch { window.localStorage.removeItem(tradingViewChartPropertiesLocalStorageKey); } }, [] ); const settingOverridesDark: Overrides | undefined = useMemo( () => getColorThemeByTheme(defaultSettingsOverridesMetadata, themeDark), [themeDark] ); const settingsOverridesLight: Overrides | undefined = useMemo( () => getColorThemeByTheme(defaultSettingsOverridesMetadata, themeLight), [themeLight] ); const getHasItBeenUpdatedColorFromTradingViewSettings = useCallback(() => { const tradingViewChartPropertiesJSON = window.localStorage.getItem( tradingViewChartPropertiesLocalStorageKey ); try { const tradingViewChartProperties = JSON.parse(tradingViewChartPropertiesJSON || ""); const defaultSettingsOverrideKeys = Object.keys(defaultSettingsOverridesMetadata); for (let index = 0; index < defaultSettingsOverrideKeys.length - 1; index++) { const defaultSettingsOverrideKey = defaultSettingsOverrideKeys[index]; const overridesPath = defaultSettingsOverrideKey.split("."); let currentValue = tradingViewChartProperties[overridesPath[0]]; if (currentValue) { let depth = 1; while (overridesPath.length !== depth && typeof currentValue !== "string") { const layerValue = currentValue[overridesPath[depth]]; if (!layerValue) break; currentValue = layerValue; depth++; } } if (typeof currentValue === "string") { const valueDark = settingOverridesDark?.[defaultSettingsOverrideKey]; const valueLight = settingsOverridesLight?.[defaultSettingsOverrideKey]; if (currentValue === valueDark || currentValue === valueLight) { continue; } else { return true; } } } } catch { // } return false; }, [settingOverridesDark, settingsOverridesLight]); const currentOverridesByTheme = currentTheme === themeDarkValue ? settingOverridesDark : settingsOverridesLight; if (needResetTradingViewLocalStorageCache) { upgradeTradingViewChartPropertiesLocalStorage(currentOverridesByTheme); } else { if (getHasItBeenUpdatedColorFromTradingViewSettings()) { mutateSettingOverrides(settingOverridesDark); mutateSettingOverrides(settingsOverridesLight); } } return currentOverridesByTheme; }; // end: Magic with colors // start: Using TradingView const defaultInterval = "60" as ResolutionString; const defaultTradingViewOptions: Omit< ChartingLibraryWidgetOptions, "container" | "interval" | "locale" | "symbol" | "overrides" | "datafeed" > = { fullscreen: true, autosize: true, library_path: "", time_frames: [ {text: "1D", resolution: "5" as ResolutionString, description: "1 day"}, {text: "3D", resolution: "15" as ResolutionString, description: "3 days"}, {text: "6D", resolution: "30" as ResolutionString, description: "6 days"}, {text: "12D", resolution: "60" as ResolutionString, description: "12 days"}, {text: "3M", resolution: "480" as ResolutionString, description: "3 month"}, {text: "6M", resolution: "1D" as ResolutionString, description: "6 month"}, ], drawings_access: {}, // studies_overrides: {}, enabled_features: [], disabled_features: [""], charts_storage_url: "", user_id: "", client_id: "", charts_storage_api_version: "1.1", }; const tradingViewIntervalLocalStorageKey = "ex-tv-interval"; const TradingViewStyled = styled.div` grid-area: trading-view; position: relative; height: 40rem; background-color: ${props => props?.theme?.["color2"]}; border-radius: 0.8rem; box-sizing: border-box; border: 1px solid; border-color: ${(props): string => props?.theme?.["color13"] || ""}; `; const TradingViewContainerStyled = styled.div<{$isLoading?: boolean}>` height: 100%; box-sizing: border-box; // fixes browser bug with border-radius overflow border: ${props => (props.$isLoading ? `1px solid ${props?.theme?.["color2"]}` : "")}; border-radius: 0.8rem; overflow: hidden; iframe { height: 100% !important; } `; const LoaderContainerStyled = styled.div` display: flex; align-items: center; justify-content: center; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: ${props => props?.theme?.["color2"]}; border-radius: 0.8rem; `; const TradingView: React.FunctionComponent<ComponentPropsType> = ({ availableLocale, selectedCurrencyPair, }) => { const tradingViewContainerRef = useRef<HTMLDivElement | null>(null); const [isTradingViewLoading, setTradingViewLoading] = useState(false); const isTablet = useIsTablet(); const [isLoadedUDFScriptState, setIsLoadedUDFScriptState] = useState<boolean>(); useEffect(() => { let isRemoved = false; loadScript(UDF_SCRIPT_PATH).then(() => { if (!isRemoved) { setIsLoadedUDFScriptState(true); } }); return () => { isRemoved = true; }; }, []); const datafeed = useMemo(() => { if (!isLoadedUDFScriptState) return; return new window.Datafeeds.UDFCompatibleDatafeed("/api/udf", 300000); }, [isLoadedUDFScriptState]); const { set: setTradingViewActivatedCache, get: getTradingViewActivatedCache, clear: clearTradingViewActivatedCache, } = useMemo(() => { return createItemCache<boolean>(false); }, []); const { set: setTradingViewIntervalCache, get: getTradingViewIntervalCache, clear: clearTradingViewIntervalCache, } = useMemo(() => { return createItemCache<ResolutionString>( (window.localStorage.getItem(tradingViewIntervalLocalStorageKey) as ResolutionString) || defaultInterval ); }, []); const { set: setTradingViewWidgetCache, get: getTradingViewWidgetCache, clear: clearTradingViewWidgetCache, } = useMemo(() => { return createItemCache<IChartingLibraryWidget>(); }, []); const [hasSelectedCurrencyPairValueState, setHasSelectedCurrencyPairValueState] = useState<boolean>(Boolean(selectedCurrencyPair)); useEffect(() => { const selectedCurrencyPairValueNew = Boolean(selectedCurrencyPair); if (selectedCurrencyPairValueNew !== hasSelectedCurrencyPairValueState) setHasSelectedCurrencyPairValueState(selectedCurrencyPairValueNew); }, [selectedCurrencyPair]); const overrides = useTradingViewOverrides(); const changeTradingViewOverrides = useCallback(() => { const tradingViewWidgetCache = getTradingViewWidgetCache(); const tradingViewActivatedCache = getTradingViewActivatedCache(); if (tradingViewWidgetCache && tradingViewActivatedCache) { if (overrides) tradingViewWidgetCache.applyOverrides(overrides); } }, [overrides]); const cssVariables = useTradingViewCSSVariables(); const changeTradingViewCSSVariables = useCallback(() => { const tradingViewWidgetCache = getTradingViewWidgetCache(); const tradingViewActivatedCache = getTradingViewActivatedCache(); if (tradingViewWidgetCache && tradingViewActivatedCache) { const tvContainer = tradingViewContainerRef?.current; if (tvContainer) { const TVIframe = tvContainer.childNodes[0] as HTMLIFrameElement; const iframeContent = TVIframe.contentDocument; if (iframeContent) { const style = document.createElement("style"); style.type = "text/css"; style.id = ""; style.innerText = `html{${cssVariables}}`; iframeContent.getElementById("")?.remove(); iframeContent.head.insertAdjacentElement("beforeend", style); } } } }, [cssVariables]); useEffect(() => { if (!tradingViewContainerRef || !tradingViewContainerRef.current) return; if (!hasSelectedCurrencyPairValueState) return; if (!isLoadedUDFScriptState) return; if (!datafeed) return; const tradingViewWidgetOld = getTradingViewWidgetCache(); if (tradingViewWidgetOld) { tradingViewWidgetOld.remove(); clearTradingViewWidgetCache(); setTradingViewActivatedCache(false); } setTradingViewLoading(true); const tradingViewIntervalCache = getTradingViewIntervalCache(); const tradingViewWidget = new widget({ ...defaultTradingViewOptions, datafeed, container: tradingViewContainerRef.current, interval: tradingViewIntervalCache, locale: availableLocale, symbol: selectedCurrencyPair, preset: isTablet ? "mobile" : undefined, }); setTradingViewWidgetCache(tradingViewWidget); tradingViewWidget.onChartReady(() => { tradingViewWidget .chart() .onIntervalChanged() .subscribe(null, (interval: ResolutionString) => { setTradingViewIntervalCache(interval); window.localStorage.setItem(tradingViewIntervalLocalStorageKey, interval); }); setTradingViewActivatedCache(true); changeTradingViewOverrides(); changeTradingViewCSSVariables(); setTradingViewLoading(false); }); return () => { tradingViewWidget.remove(); clearTradingViewWidgetCache(); }; }, [isLoadedUDFScriptState, hasSelectedCurrencyPairValueState, availableLocale, isTablet]); useEffect(changeTradingViewOverrides, [changeTradingViewOverrides]); useEffect(changeTradingViewCSSVariables, [changeTradingViewCSSVariables]); useEffect(() => { const tradingViewWidgetCache = getTradingViewWidgetCache(); const tradingViewActivatedCache = getTradingViewActivatedCache(); if (tradingViewWidgetCache && tradingViewActivatedCache) { const tradingViewIntervalCache = getTradingViewIntervalCache(); if (!selectedCurrencyPair || !tradingViewIntervalCache) return; tradingViewWidgetCache.setSymbol(selectedCurrencyPair, tradingViewIntervalCache, () => { // }); } }, [selectedCurrencyPair]); useEffect(() => { return () => { clearTradingViewIntervalCache(); clearTradingViewActivatedCache(); }; }, []); return ( <TradingViewStyled> <TradingViewContainerStyled $isLoading={isTradingViewLoading} ref={tradingViewContainerRef} /> {isTradingViewLoading && ( <LoaderContainerStyled> <Preloader /> </LoaderContainerStyled> )} </TradingViewStyled> ); }; export default TradingView;
Updating the library was not enough. It turned out that I missed two details: TradingView does not have all the languages that Merkeleon offers to the client. Why is it desirable? The BTC USD chart is tracked by users from different countries. It is crucial for them to understand how to use TradingView library. And one more catch: earlier, the function of drawing charts in real time had been installed. It went down.
It was a no-brainer task. If there is no desired language in TradingView, we show the chart in English. After digging into the library’s typification, I found an enum with possible languages. We should come up with a solution that would check whether the typification was updated after the library update. Since clients can connect any language, we need to know if the client has a user-selected language.
I created a control card to regularly check if TradingView has this language. The type of the control card was Required<{[localeKey in TradingViewLanguageCode]: null}>. This solution takes up memory because a new Instance is created. It exists while the project is open in the browser. Such an approach gives full control over changes to the language base in the library. It also helps to verify the language selected by the user. The update to 2023 version demonstrated that the idea works: a new language was added, and the code responded.
import type {LanguageCode as TradingViewLanguageCode} from "libraries/TradingView/charting_library/charting_library.d"; import {widget} from "libraries/TradingView/charting_library/charting_library.esm.js"; type AvailableLanguageType = TradingViewLanguageCode; type AvailableLanguagesMapType = Required<{[localeKey in AvailableLanguageType]: null}>; const FALLBACK_LOCALE = "en"; const availableLanguagesMap: AvailableLanguagesMapType = { ar: null, zh: null, cs: null, da_DK: null, ca_ES: null, nl_NL: null, en: null, et_EE: null, fr: null, de: null, el: null, he_IL: null, hu_HU: null, id_ID: null, it: null, ja: null, ko: null, fa: null, pl: null, pt: null, ro: null, ru: null, sk_SK: null, es: null, sv: null, th: null, tr: null, vi: null, no: null, ms_MY: null, zh_TW: null, }; const availableLanguages = Object.keys(availableLanguagesMap); const getAvailableLocale = (locale) => { if (!availableLanguages.includes(locale)) return FALLBACK_LOCALE as AvailableLanguageType; return locale as AvailableLanguageType; } const tradingViewWidget = new widget({ ...anyProps, locale: availableLocale, });
To restore the rendering of charts in real time turned out to be much more complex. At this stage, I learned that in the public domain, there was a project with the source code that built a JS UDF script.
Let’s make it so that CustomEvent would be triggered during the transaction. JS UDF would catch it and respond. To tackle the problem, it was necessary to study the new repository, characteristics and technical conditions. It took time. But I understood how to continue working with the repository. I also found out that TradingView does not allow redrawing the Bar through the script. You can only work with the latter. But you need to track when to update and when to draw a new one based on the data about the completed transaction transmitted to onTick. I did this in a separate project with the source code of the JS UDF script.
Studying in detail the sources for the JS UDF script, I saw that TradingView provided tools for settling the task with headers in the header. Although it could be fixed without access to the source code.
After the changes, it was time to update the library to the April 2023 version. After the update, inside the typification, there appeared comments explaining the properties that were also in a separate JS UDF script project. And that’s a plus. Besides, I will highlight the uninterrupted compatibility and the absence of radical challenges with the interaction of updates with the written part of our functionality, or rather with customizations in JS UDF.
In this TradingView guide, I described my experience with the wide functionality of the instrument, its advantages, flexibility. On top of that, I want to make a few recommendations related to the specifics of using the library in our product:
My next task is to improve the work with the chart on our side. To do this, I need to understand the mechanism of storing drafts made by the user and added comments on the chart, and save them for subsequent display in case of forced re-renders. I will also modify the JS UDF. So, regardless of trading, without interfering into the backend, I can open a candle in the started timeframe. But it will be another story.
Was this article helpful? If you have questions or suggestions about integrations, please contact us. Your feedback will help us create relevant content for you.
Leading frontend engineer at Merkeleon.com (React/Redux – Middle+++) and backend developer (.Net – Middle-).
Merkeleon has been operating since 2008. Since then, the company has been providing businesses software solutions for e-procurement and trading platforms with multiple sellers. For 10 years, the company has also been developing software for crypto businesses.