Closed jurepetrovic closed 2 years ago
You need to render an element with a ref that you can use to get a reference to the dom element and mount the chart.
I did a custom react component for now. Just for test, but it seems ok for now. Obviously, I will need to add some props and state.
import * as React from 'react';
import { createChart } from 'lightweight-charts';
export class LightweightChart extends React.PureComponent {
static defaultProps = {
containerId: 'lightweight_chart_container',
};
chart = null;
componentDidMount() {
const chart = createChart(this.props.containerId, { width: 800, height: 600 });
this.chart = chart;
const lineSeries = chart.addLineSeries();
lineSeries.setData([
{ time: '2019-04-10', value: 60.01 },
{ time: '2019-04-11', value: 80.01 },
]);
const barSeries = chart.addBarSeries({
thinBars: false,
});
// set the data
barSeries.setData([
{ time: "2019-04-10", open: 141.77, high: 170.39, low: 120.25, close: 145.72 },
{ time: "2019-04-11", open: 145.72, high: 147.99, low: 100.11, close: 108.19 },
]);
}
componentWillUnmount() {
if (this.chart !== null) {
this.chart.remove();
this.chart = null;
}
}
render() {
return (
<div
id={ this.props.containerId }
className={ 'LightweightChart' }
/>
);
}
}
Do you see any problems with that?
Personally I don't code React apps that way. I always keep the state outside of the component. That said, your approach is fine, except that personally I would use a ref instead of a static id. (If you have a static id, you can't put more than one chart in your screen by the way.)
Yes, I noticed that while reading your "refs" link. Since I don't need more than one for now, I'll keep it simple ;)
How do you mean state outside component? You make extra component for the state or you keep it in parent component?
Thanks for your help!
@jurepetrovic were you able to resolve this issue? Still having this issue: lightweight-charts.esm.production.js:7 Uncaught (in promise) Error: Value is null
Yes, it worked with code above. Then I just included
Yea this is not a solution as it creates another chart upon introducing state data.
I tackled this issue by using hooks in 2 separate useEffects as such :
import React from 'react';
import { createChart } from 'lightweight-charts';
let chart
let candlestickSeries
export default ({lastCandle}) => {
const myRef = React.useRef()
// console.log('candles' , lastCandle)
React.useEffect(() => {
chart = createChart(myRef.current, { width: 1000, height: 800 });
chart.applyOptions({
timeScale: {
rightOffset: 45,
barSpacing: 15,
lockVisibleTimeRangeOnResize: true,
rightBarStaysOnScroll: true,
},
priceScale: {
position: 'right',
// mode: 1,
autoScale: false,
// invertScale: true,
alignLabels: true,
borderVisible: false,
borderColor: '#555ffd',
scaleMargins: {
top: 0.65,
bottom: 0.25,
},
crosshair: {
vertLine: {
color: '#6A5ACD',
width: 0.5,
style: 1,
visible: true,
labelVisible: false,
},
horzLine: {
color: '#6A5ACD',
width: 0.5,
style: 0,
visible: true,
labelVisible: true,
},
mode: 1,
},
grid: {
vertLines: {
color: 'rgba(70, 130, 180, 0.5)',
style: 1,
visible: true,
},
horzLines: {
color: 'rgba(70, 130, 180, 0.5)',
style: 1,
visible: true,
},
},
},
});
candlestickSeries = chart.addCandlestickSeries({
upColor: '#0B6623',
downColor: '#FF6347',
borderVisible: false,
wickVisible: true,
borderColor: '#000000',
wickColor: '#000000',
borderUpColor: '#4682B4',
borderDownColor: '#A52A2A',
wickUpColor: "#4682B4",
wickDownColor: "#A52A2A",
});
}, []);
React.useEffect(() => {
candlestickSeries.update(lastCandle);
}, [lastCandle]);
return (
<>
<div ref={myRef} id='chart' />
</>
);
}
Here's the simplest implementation I could come up with using hooks.
import React, { useEffect } from 'react';
import { createChart } from 'lightweight-charts';
function Chart() {
const ref = React.useRef();
useEffect(() => {
const chart = createChart(ref.current, { width: 400, height: 300 });
const lineSeries = chart.addLineSeries();
lineSeries.setData([
{ time: '2019-04-11', value: 80.01 },
{ time: '2019-04-12', value: 96.63 },
{ time: '2019-04-13', value: 76.64 },
{ time: '2019-04-14', value: 81.89 },
{ time: '2019-04-15', value: 74.43 },
{ time: '2019-04-16', value: 80.01 },
{ time: '2019-04-17', value: 96.63 },
{ time: '2019-04-18', value: 76.64 },
{ time: '2019-04-19', value: 81.89 },
{ time: '2019-04-20', value: 74.43 },
]);
}, []);
return (
<>
<div ref={ref} />
</>
);
}
export default Chart;
Here's a fairly good react wrapper: https://github.com/Kaktana/kaktana-react-lightweight-charts#readme
Anyone figured out how to do it using auto height and auto width ? Thanks
Here's the simplest implementation I could come up with using hooks.
import React, { useEffect } from 'react'; import { createChart } from 'lightweight-charts'; function Chart() { const ref = React.useRef(); useEffect(() => { const chart = createChart(ref.current, { width: 400, height: 300 }); const lineSeries = chart.addLineSeries(); lineSeries.setData([ { time: '2019-04-11', value: 80.01 }, { time: '2019-04-12', value: 96.63 }, { time: '2019-04-13', value: 76.64 }, { time: '2019-04-14', value: 81.89 }, { time: '2019-04-15', value: 74.43 }, { time: '2019-04-16', value: 80.01 }, { time: '2019-04-17', value: 96.63 }, { time: '2019-04-18', value: 76.64 }, { time: '2019-04-19', value: 81.89 }, { time: '2019-04-20', value: 74.43 }, ]); }, []); return ( <> <div ref={ref} /> </> ); } export default Chart;
For anyone having problem with duplicated chart when saving, just add a return inside useEffect
useEffect(() => {
//... code
return () => {
chart.remove()
}
}, [])
Anyone figured out how to do it using auto height and auto width ? Thanks
@IbrahimSam96 In my case I just omit the "with" to get 100%
Thanks for your response! Here is another way doing it by attaching an event listener "resize" and removing it in the useEfect clean up.
const LivePortfolioGraph = (props) => {
const [width, setWidth] = useState(0);
const [zobe, setZobe] = useState([]);
useEffect( ()=> {
const handleResize = _debounce(() => {chart.applyOptions({width: props.g.current.clientWidth, }); } ,100 );
window.addEventListener("resize", handleResize);
const chart = createChart(props.g.current, {
width: width,
height: 450,
layout: {
textColor: '#d1d4dc',
backgroundColor: '#131722',
},
rightPriceScale: {
scaleMargins: {
top: 0.3,
bottom: 0.25,
},
},
timeScale: {
visible: true,
},
crosshair: {
vertLine: {
width: 5,
color: 'rgba(224, 227, 235, 0.1)',
style: 0,
},
horzLine: {
visible: false,
labelVisible: true,
},
},
localization: {
priceFormatter: price => {
const nf = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' })
return nf.format(price)
},
},
grid: {
vertLines: {
color: 'rgba(42, 46, 57, 0)',
},
horzLines: {
color: 'rgba(42, 46, 57, 0)',
},
},
});
var areaSeries = chart.addAreaSeries({
topColor: 'rgba(38, 198, 218, 0.04)',
bottomColor: 'rgba(38, 198, 218, 0.04)',
lineColor: 'rgba(38, 198, 218, 1)',
lineWidth: 2,
crossHairMarkerVisible: false,
});
// Clean Up
return () => {
window.removeEventListener('resize', handleResize);
}
}, [zobe])
return (
<div style={{position:"relative", left:"5%", height:"100%", width:"100%", display:"none"}}>
</div>
)
}
export default LivePortfolioGraph;
Here's the simplest implementation I could come up with using hooks.
import React, { useEffect } from 'react'; import { createChart } from 'lightweight-charts'; function Chart() { const ref = React.useRef(); useEffect(() => { const chart = createChart(ref.current, { width: 400, height: 300 }); const lineSeries = chart.addLineSeries(); lineSeries.setData([ { time: '2019-04-11', value: 80.01 }, { time: '2019-04-12', value: 96.63 }, { time: '2019-04-13', value: 76.64 }, { time: '2019-04-14', value: 81.89 }, { time: '2019-04-15', value: 74.43 }, { time: '2019-04-16', value: 80.01 }, { time: '2019-04-17', value: 96.63 }, { time: '2019-04-18', value: 76.64 }, { time: '2019-04-19', value: 81.89 }, { time: '2019-04-20', value: 74.43 }, ]); }, []); return ( <> <div ref={ref} /> </> ); } export default Chart;
Anyone managed to get this working in typescript? For me it doesn't like the fact that ref.current
is of type undefined
:
Argument of type 'undefined' is not assignable to parameter of type 'string | HTMLElement'. TS2345
30 |
31 | useEffect(() => {
> 32 | const chart = createChart(ref.current, { width: 400, height: 300 })
| ^
33 | const candlestickSeries = chart.addCandlestickSeries()
34 | candlestickSeries.setData(candleData)
35 | }, [])
@jopfre Thanks for https://github.com/tradingview/lightweight-charts/issues/200#issuecomment-748493961! I was running into bugs with the React wrapper https://github.com/Kaktana/kaktana-react-lightweight-charts that I'm not encountering with your approach.
Here's the simplest implementation I could come up with using hooks.
Thanks for this @jopfre !
I'm still getting an error...how do we then use this in another component's render
function?
Note: I passed in the ref trying to fix the issue and its still happening...
render() {
return (
<>
{Chart(React.useRef())}
</>
);
}
?? TIA!
Side note...I'm a mobile developer learning react...why are hooks needed for this at all?
UPDATE: Got it working...~really don't know how~ 😅 I tried some stuff...then hit Ctl+Z until I was back to a certain point...and then it worked. Typical 😂
here's how
render() {
return (
<>
<Chart />
</>
);
}
I noticed that useEffect
isn't getting called after the first render.
Is anyone else seeing this?
Problem here is the chart isn't receiving updated params when the state changes in the parent component.
UPDATE
If I add the arrays of data I use to the dependency list in useEffect
then the chart WILL update.
However, a new/updated chart will get added, BUT the old one will not get removed. So I end up with N number of charts where N is the number of times render
was called in the wrapping component.
Halp.
UPDATE 2 Used an approach based on this guy's code...not using hooks...and it works :) https://github.com/Kaktana/kaktana-react-lightweight-charts/blob/master/src/kaktana-react-lightweight-charts.js
import React from "react";
import { createChart } from 'lightweight-charts';
class ChartWrapper extends React.Component {
state = {
}
constructor(props) {
super(props);
this.chartDiv = React.createRef();
}
componentDidMount() {
this.chart = createChart(this.chartDiv.current, {
width: 800, height: 400,
timeScale: {
timeVisible: true,
secondsVisible: false,
},
watermark: {
visible: true,
fontSize: 34,
color: 'rgba(0, 0, 0, 0.25)',
},
});
window.addEventListener("resize", this.resizeHandler);
this.resizeHandler();
}
componentDidUpdate() {
this.addPriceData(this.chart, this.props.priceData, this.props.tradeData)
// "Setting state" for the chart
var options = {
watermark: {
text: this.props.waterData
}
}
this.chart.applyOptions(options)
}
render() {
return (
<div ref={this.chartDiv}
style={{ position: "relative" }}>
</div>
);
}
// ------------------------------------------------------------------------
resizeHandler = () => {
this.chart.resize(this.chartDiv.current.parentNode.clientWidth, this.chartDiv.current.parentNode.clientHeight);
}
addLineData(chart, data) {
const lineSeries = chart.addLineSeries();
lineSeries.setData(data)
console.log('added data to chart', data)
return lineSeries
}
addPriceData(chart, data, tradeData) {
if (!data) {
return
}
if (data[0].value) {
const lineSeries = this.addLineData(chart, data)
this.addTradeMarkers(lineSeries, tradeData)
} else if (data[0].open) {
const lineSeries = chart.addCandlestickSeries();
lineSeries.setData(data)
this.addTradeMarkers(lineSeries, tradeData)
}
}
addTradeMarkers(series, tradeData) {
if (!series || !tradeData) {
return
}
var markers = []
tradeData.forEach((trade) => {
if (trade.action === 'sell') {
markers.push({ time: trade.time, position: 'aboveBar', color: '#e91e63', shape: 'arrowDown', text: 'Sell ' + trade.quantity });
} else {
markers.push({ time: trade.time, position: 'belowBar', color: '#2196F3', shape: 'arrowUp', text: 'Buy ' + trade.quantity });
}
})
series.setMarkers(markers);
}
}
export default ChartWrapper
useEffect(() => { const chart = createChart(ref.current, { width: 400, height: 300 }); const lineSeries = chart.addLineSeries(); lineSeries.setData([ { time: '2019-04-11', value: 80.01 }, { time: '2019-04-12', value: 96.63 }, { time: '2019-04-13', value: 76.64 }, { time: '2019-04-14', value: 81.89 }, { time: '2019-04-15', value: 74.43 }, { time: '2019-04-16', value: 80.01 }, { time: '2019-04-17', value: 96.63 }, { time: '2019-04-18', value: 76.64 }, { time: '2019-04-19', value: 81.89 }, { time: '2019-04-20', value: 74.43 }, ]); }, []);
I was able to get this working in TS by wrapping everything in the useEffect with a ref.current
null check
useEffect(() => {
if (ref.current) {
const chart = createChart(ref.current, { width: 400, height: 300 });
const lineSeries = chart.addLineSeries();
lineSeries.setData([
{ time: '2019-04-11', value: 80.01 },
{ time: '2019-04-12', value: 96.63 },
{ time: '2019-04-13', value: 76.64 },
{ time: '2019-04-14', value: 81.89 },
{ time: '2019-04-15', value: 74.43 },
{ time: '2019-04-16', value: 80.01 },
{ time: '2019-04-17', value: 96.63 },
{ time: '2019-04-18', value: 76.64 },
{ time: '2019-04-19', value: 81.89 },
{ time: '2019-04-20', value: 74.43 },
]);
}
}, [ref]);
Anyone managed to get this working in typescript? For me it doesn't like the fact that
ref.current
is of typeundefined
:Argument of type 'undefined' is not assignable to parameter of type 'string | HTMLElement'. TS2345 30 | 31 | useEffect(() => { > 32 | const chart = createChart(ref.current, { width: 400, height: 300 }) | ^ 33 | const candlestickSeries = chart.addCandlestickSeries() 34 | candlestickSeries.setData(candleData) 35 | }, [])
@cryptofish7 I fixed it by passing a parameter:
function Chart(this: any) {
const ref = React.useRef(this);
...
return (....);
And using in the way:
<Flex>
<Chart />
</Flex>
Here's the simplest implementation I could come up with using hooks.
import React, { useEffect } from 'react'; import { createChart } from 'lightweight-charts'; function Chart() { const ref = React.useRef(); useEffect(() => { const chart = createChart(ref.current, { width: 400, height: 300 }); const lineSeries = chart.addLineSeries(); lineSeries.setData([ { time: '2019-04-11', value: 80.01 }, { time: '2019-04-12', value: 96.63 }, { time: '2019-04-13', value: 76.64 }, { time: '2019-04-14', value: 81.89 }, { time: '2019-04-15', value: 74.43 }, { time: '2019-04-16', value: 80.01 }, { time: '2019-04-17', value: 96.63 }, { time: '2019-04-18', value: 76.64 }, { time: '2019-04-19', value: 81.89 }, { time: '2019-04-20', value: 74.43 }, ]); }, []); return ( <> <div ref={ref} /> </> ); } export default Chart;
Anybody have tips on how to do legends or floating tooltips with this setup? They use document.body to place the chart and then add stuff to that -- not sure how to work with the useRef().current in the same way.
import React, { useEffect, useRef } from 'react';
import { gql, useQuery } from '@apollo/client';
import { createChart } from "lightweight-charts";
import { CircularProgress } from '@mui/material';
//import styled from 'styled-components'
/*
const StyledChart = styled.div`
background-color: red;
`*/
const Bitquery = () =>{
const chartRef = useRef(null);
const CHART_DATA = gql`
query chart{
ethereum(network: bsc) {
dexTrades(
options: {limit: 1000, asc: "timeInterval.minute"}
date: {since: "2021-10-26"}
exchangeName: {is: "Pancake"}
baseCurrency: {is: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"}
quoteCurrency: {is: "0xe9e7cea3dedca5984780bafc599bd69add087d56"}
) {
timeInterval {
minute(count: 5)
}
baseCurrency {
symbol
address
}
baseAmount
quoteCurrency {
symbol
address
}
quoteAmount
trades: count
quotePrice
maximum_price: quotePrice(calculate: maximum)
minimum_price: quotePrice(calculate: minimum)
open_price: minimum(of: block, get: quote_price)
close_price: maximum(of: block, get: quote_price)
}
}
}
`;
useEffect(()=> {
if(chartRef.current){
const chart = createChart(chartRef.current, {
width: 600,
height: 300,
localization: {
dateFormat: 'yyyy-mm-dd HH:mm:ss',
}
});
prepareChart(chart);
}
}, [])
const { loading, data } = useQuery(CHART_DATA);
console.log(data);
function prepareChart(chart){
let changedData = [];
var areaSeries = chart.addAreaSeries();
data.ethereum.dexTrades.map((chart_) => {
changedData.push({
time: chart_.timeInterval.minute,
value: chart_.maximum_price
});
console.log(changedData);
return areaSeries.setData(changedData);
})
}
if (loading){ return (<> < CircularProgress /></>) }
else{
return (<><div ref={chartRef} /></>);
}
}
export default Bitquery;
This code only return the chart one time. After the refresh this stop working.
This code only return the chart one time. After the refresh this stop working.
I'm new to React but my understanding is that in useEffect, you have an empty array at the end so it will only load once -- basically, you want to put something in there that you want to trigger a refresh (I use the data object used to populate the chart)
This code only return the chart one time. After the refresh this stop working.
I'm new to React but my understanding is that in useEffect, you have an empty array at the end so it will only load once -- basically, you want to put something in there that you want to trigger a refresh (I use the data object used to populate the chart)
Thank you just the despair didn't allowed me to perceive that.
Here's the simplest implementation I could come up with using hooks.
import React, { useEffect } from 'react'; import { createChart } from 'lightweight-charts'; function Chart() { const ref = React.useRef(); useEffect(() => { const chart = createChart(ref.current, { width: 400, height: 300 }); const lineSeries = chart.addLineSeries(); lineSeries.setData([ { time: '2019-04-11', value: 80.01 }, { time: '2019-04-12', value: 96.63 }, { time: '2019-04-13', value: 76.64 }, { time: '2019-04-14', value: 81.89 }, { time: '2019-04-15', value: 74.43 }, { time: '2019-04-16', value: 80.01 }, { time: '2019-04-17', value: 96.63 }, { time: '2019-04-18', value: 76.64 }, { time: '2019-04-19', value: 81.89 }, { time: '2019-04-20', value: 74.43 }, ]); }, []); return ( <> <div ref={ref} /> </> ); } export default Chart;
Anyone managed to get this working in typescript? For me it doesn't like the fact that
ref.current
is of typeundefined
:Argument of type 'undefined' is not assignable to parameter of type 'string | HTMLElement'. TS2345 30 | 31 | useEffect(() => { > 32 | const chart = createChart(ref.current, { width: 400, height: 300 }) | ^ 33 | const candlestickSeries = chart.addCandlestickSeries() 34 | candlestickSeries.setData(candleData) 35 | }, [])
I have to admit that the following type assertion is not beautiful, but it does its job:
const ref = useRef() as React.MutableRefObject<HTMLDivElement>;
Here's a whole chart component.
import * as React from 'react';
import { createChart } from 'lightweight-charts';
import { useEffect, useRef, useState } from 'react'
interface ILineChart {
}
const LineChart: React.FunctionComponent<ILineChart> = (props) => {
const ref = useRef() as React.MutableRefObject<HTMLDivElement>;
useEffect(() => {
const chart = createChart(ref.current, { width: 400, height: 300 });
const lineSeries = chart.addLineSeries();
lineSeries.setData([
{ time: '2019-04-11', value: 80.01 },
{ time: '2019-04-12', value: 96.63 },
{ time: '2019-04-13', value: 76.64 },
{ time: '2019-04-14', value: 81.89 },
{ time: '2019-04-15', value: 74.43 },
{ time: '2019-04-16', value: 80.01 },
{ time: '2019-04-17', value: 96.63 },
{ time: '2019-04-18', value: 76.64 },
{ time: '2019-04-19', value: 81.89 },
{ time: '2019-04-20', value: 74.43 },
]);
return () => {
chart.remove()
}
}, []);
return (
<>
<div ref={ref} />
</>
)
};
export default LineChart;
Btw we're going to prepare a tutorial how to make lightweight-charts work with react app so if anybody we'll have a time to look at #958 - feel free to leave any suggestions/ideas there.
The tutorials are deployed here now https://tradingview.github.io/lightweight-charts/tutorials. If you'll have any questions/suggestions/feedback - please create a discussion/issue.
Can you add a tutorial on how to add a tooltip with react? thank you!
The tutorials are deployed here now https://tradingview.github.io/lightweight-charts/tutorials. If you'll have any questions/suggestions/feedback - please create a discussion/issue.
How can I integrate this with React? Do I have to create custom react component?
Regards, Jure