React加密货币项目中TradingView Widget绘制水平直线咨询
问题
我已经在React加密货币项目中集成了TradingView Advanced Real-Time Chart Widget,现在需要通过代码根据后端返回的价格数组在对应价位绘制水平直线(比如后端返回价格29000时,就在该价位画一条线),不想使用Widget自带的绘图工具。我了解TradingView轻量图表库,但它缺少我需要的绘图、标注等功能,而且没找到这个Widget的官方API文档,想咨询实现方法。
附上我的Chart组件代码:
/* eslint-disable new-cap */ /* eslint-disable no-return-assign */ /* eslint-disable no-new */ /* eslint-disable @typescript-eslint/no-use-before-define */ import React, { useEffect, useRef } from "react"; import s from "./Chart.module.scss"; let tvScriptLoadingPromise; export default function Chart() { const onLoadScriptRef = useRef(); useEffect(() => { onLoadScriptRef.current = createWidget; if (!tvScriptLoadingPromise) { tvScriptLoadingPromise = new Promise((resolve) => { const script = document.createElement("script"); script.id = "tradingview-widget-loading-script"; script.src = "https://s3.tradingview.com/tv.js"; script.type = "text/javascript"; script.onload = resolve; document.head.appendChild(script); }); } tvScriptLoadingPromise.then( () => onLoadScriptRef.current && onLoadScriptRef.current() ); return () => (onLoadScriptRef.current = null); function createWidget() { if ( document.getElementById("tradingview_81848") && "TradingView" in window ) { const widget = new window.TradingView.widget({ width: 1000, height: 680, symbol: "BINANCE:BTCUSDT", interval: "D", timezone: "Etc/UTC", theme: "dark", style: "1", locale: "en", toolbar_bg: "#f1f3f6", enable_publishing: false, hide_side_toolbar: false, allow_symbol_change: true, container_id: "tradingview_81848", }); } } }, []); return ( <div className="tradingview-widget-container"> <div id="tradingview_81848" /> </div> ); }
解决方案
方法1:利用Widget内部未公开的Chart API
TradingView的Widget虽然没有公开完整官方API,但可通过onChartReady回调获取内部图表实例,调用绘图方法实现需求,这是最贴合图表原生体验的方案:
/* eslint-disable new-cap */ /* eslint-disable no-return-assign */ /* eslint-disable no-new */ /* eslint-disable @typescript-eslint/no-use-before-define */ import React, { useEffect, useRef } from "react"; import s from "./Chart.module.scss"; let tvScriptLoadingPromise; // 模拟后端返回的价格数组,实际可替换为接口数据 const priceLevels = [29000, 30000, 31000]; export default function Chart() { const onLoadScriptRef = useRef(); const widgetRef = useRef(null); useEffect(() => { onLoadScriptRef.current = createWidget; if (!tvScriptLoadingPromise) { tvScriptLoadingPromise = new Promise((resolve) => { const script = document.createElement("script"); script.id = "tradingview-widget-loading-script"; script.src = "https://s3.tradingview.com/tv.js"; script.type = "text/javascript"; script.onload = resolve; document.head.appendChild(script); }); } tvScriptLoadingPromise.then( () => onLoadScriptRef.current && onLoadScriptRef.current() ); return () => (onLoadScriptRef.current = null); function createWidget() { if ( document.getElementById("tradingview_81848") && "TradingView" in window ) { widgetRef.current = new window.TradingView.widget({ width: 1000, height: 680, symbol: "BINANCE:BTCUSDT", interval: "D", timezone: "Etc/UTC", theme: "dark", style: "1", locale: "en", toolbar_bg: "#f1f3f6", enable_publishing: false, hide_side_toolbar: false, allow_symbol_change: true, container_id: "tradingview_81848", // 图表就绪后执行绘图逻辑 onChartReady: function() { const chart = widgetRef.current.chart(); priceLevels.forEach(price => { // 创建水平直线 chart.createShape( { price: price, shape: "horizontalLine", color: "#ff0000", lineWidth: 2, lineStyle: 1, // 1=实线,2=虚线 text: `${price}`, textLocation: "right" }, { overwrite: true } // 覆盖同价位已有形状 ); }); } }); } } }, []); return ( <div className="tradingview-widget-container"> <div id="tradingview_81848" /> </div> ); }
关键说明:
- 通过
widget.chart()获取内部图表实例 createShape方法支持自定义线条样式、颜色和标注文本- 该方案依赖Widget内部实现,虽无公开文档,但实际项目中稳定性良好
方法2:DOM层叠加绘制(兼容所有版本)
如果担心内部API变动,可采用DOM叠加方式,计算价格对应Y坐标后绘制直线,完全不依赖Widget内部逻辑:
/* eslint-disable new-cap */ /* eslint-disable no-return-assign */ /* eslint-disable no-new */ /* eslint-disable @typescript-eslint/no-use-before-define */ import React, { useEffect, useRef, useState } from "react"; import s from "./Chart.module.scss"; let tvScriptLoadingPromise; const priceLevels = [29000, 30000, 31000]; export default function Chart() { const onLoadScriptRef = useRef(); const widgetRef = useRef(null); const chartContainerRef = useRef(null); const [priceRange, setPriceRange] = useState({ min: 0, max: 0 }); useEffect(() => { onLoadScriptRef.current = createWidget; if (!tvScriptLoadingPromise) { tvScriptLoadingPromise = new Promise((resolve) => { const script = document.createElement("script"); script.id = "tradingview-widget-loading-script"; script.src = "https://s3.tradingview.com/tv.js"; script.type = "text/javascript"; script.onload = resolve; document.head.appendChild(script); }); } tvScriptLoadingPromise.then( () => onLoadScriptRef.current && onLoadScriptRef.current() ); return () => (onLoadScriptRef.current = null); function createWidget() { if ( document.getElementById("tradingview_81848") && "TradingView" in window ) { widgetRef.current = new window.TradingView.widget({ width: 1000, height: 680, symbol: "BINANCE:BTCUSDT", interval: "D", timezone: "Etc/UTC", theme: "dark", style: "1", locale: "en", toolbar_bg: "#f1f3f6", enable_publishing: false, hide_side_toolbar: false, allow_symbol_change: true, container_id: "tradingview_81848", onChartReady: function() { const chart = widgetRef.current.chart(); const priceScale = chart.priceScale(); // 获取初始价格范围 const initialRange = priceScale.getVisibleRange(); setPriceRange({ min: initialRange.from, max: initialRange.to }); // 监听价格范围变化,缩放/平移时更新线条位置 priceScale.subscribeVisibleRangeChange(newRange => { setPriceRange({ min: newRange.from, max: newRange.to }); }); } }); } } }, []); // 渲染水平线条 const renderPriceLines = () => { if (priceRange.min === 0 || priceRange.max === 0) return null; const containerRect = chartContainerRef.current.getBoundingClientRect(); const chartHeight = containerRect.height; const priceDiff = priceRange.max - priceRange.min; return priceLevels.map((price, index) => { // 计算Y坐标:图表顶部对应高价,所以价格越高Y值越小 const yPos = chartHeight - ((price - priceRange.min) / priceDiff) * chartHeight; return ( <div key={index} style={{ position: "absolute", top: `${yPos}px`, left: 0, width: "100%", height: "1px", backgroundColor: "#ff0000", pointerEvents: "none", // 不干扰图表交互 zIndex: 10 }} /> ); }); }; return ( <div className="tradingview-widget-container" ref={chartContainerRef} style={{ position: "relative" }}> <div id="tradingview_81848" /> {renderPriceLines()} </div> ); }
关键说明:
- 通过
priceScale().getVisibleRange()获取当前可见价格区间 - 根据价格比例计算Y坐标,实现线条与图表价格对齐
pointerEvents: none避免干扰图表的点击、缩放等交互- 监听价格范围变化,确保线条在图表视图变更时自动更新位置
内容的提问来源于stack exchange,提问作者Zhenya
相关产品推荐
相关产品推荐

