Open kentdev92 opened 4 years ago
Hi,
There is currently no relative function for update()
. However, you can just change the series values and it will automatically update the chart in realtime.
I'll look into the update
function to maybe implement it.
Hi, There is currently no relative function for
update()
. However, you can just change the series values and it will automatically update the chart in realtime.I'll look into the
update
function to maybe implement it.
Hi @AurelReb,
My partner and I are working to making a live lineseries chart but were unable to simply add objects to the lineseries data variable to get the chart update. How do you recommend we add new data to the lineseries without the update() function from the original library?
Cheers, Jared
Hi, Here is an example:
class App extends Component {
constructor(props) {
super(props);
this.state = {
options: {
alignLabels: true,
timeScale: {
rightOffset: 12,
barSpacing: 3,
fixLeftEdge: true,
lockVisibleTimeRangeOnResize: true,
rightBarStaysOnScroll: true,
borderVisible: false,
borderColor: "#fff000",
visible: true,
timeVisible: true,
secondsVisible: false
}
},
lineSeries: [{
data: [
{ 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 },
]
}]
}
}
addValue = () => {
let newlineSeries = [...this.state.lineSeries, {time: '2019-04-21', value: 100}];
this.setState({lineSeries: newlineSeries});
render() {
return (
<>
<Chart options={this.state.options} lineSeries={this.state.lineSeries} autoWidth height={320} />
<button onClick={this.addValue}>Add</button>
</>
)
}
}
When you click on the button, the value 100 will automatically be added to the series. You can do the same for your live update.
This issue was why I had to transition away from kaktana charts. I found a way to do it with React.useEffect() (Requires react > 16.8 I believe?) directly using lightweight-charts:
import React from 'react';
import { createChart } from 'lightweight-charts';
import PropTypes from 'prop-types';
const HEIGHT = 300;
let chart;
let candlestickSeries;
const CandleChart = ({legend, initCandles, lastCandle, decimals}) => {
const chartRef = React.useRef();
const legendRef = React.useRef();
React.useEffect(() => {
chart = createChart(chartRef.current, {
width: chartRef.current.offsetWidth,
height: HEIGHT,
alignLabels: true,
timeScale: {
rightOffset: 0,
barSpacing: 15,
fixLeftEdge: false,
lockVisibleTimeRangeOnResize: true,
rightBarStaysOnScroll: true,
borderVisible: false,
borderColor: '#fff000',
visible: true,
timeVisible: true,
secondsVisible: false
},
rightPriceScale: {
scaleMargins: {
top: 0.3,
bottom: 0.25,
},
borderVisible: false,
},
priceScale: {
autoScale: true,
},
watermark: {
color: 'rgba(0, 0, 0, 0.7)',
visible: true,
text: 'TxQuick',
fontSize: 18,
horzAlign: 'left',
vertAlign: 'bottom',
},
});
candlestickSeries = chart.addCandlestickSeries({
priceScaleId: 'right',
upColor: '#00AA00',
downColor: '#AA0000',
borderVisible: false,
wickVisible: true,
borderColor: '#000000',
wickColor: '#000000',
borderUpColor: '#00AA00',
borderDownColor: '#AA0000',
wickUpColor: '#00AA00',
wickDownColor: '#AA0000',
priceFormat: {
type: 'custom',
minMove: '0.00000001',
formatter: (price) => {
return parseFloat(price).toFixed(decimals);
}
},
});
candlestickSeries.setData(initCandles);
}, []);
React.useEffect(() => {
candlestickSeries.update(lastCandle);
}, [lastCandle]);
React.useEffect(() => {
const handler = () => {
chart.resize(chartRef.current.offsetWidth, HEIGHT);
};
window.addEventListener('resize', handler);
return () => {
window.removeEventListener('resize', handler);
};
}, []);
return (
<div ref={chartRef} id='chart' style={{'position': 'relative', 'width': '100%'}}>
<div
ref={legendRef}
style={{
position: 'absolute',
zIndex: 2,
color: '#333',
padding: 10,
}}
>
{legend}
</div>
</div>
);
};
CandleChart.propTypes = {
legend: PropTypes.string,
initCandles: PropTypes.array,
lastCandle: PropTypes.object,
decimals: PropTypes.number,
};
export default CandleChart;
My data was coming in at 1s intervals, so you couldn't navigate around the chart at all because it would render the chart fresh with every update. This fixes that issue. This also auto-resizes and implements a legend.
Maybe this approach could be worked into kaktana charts?
Regardless, I hope this helps someone out there. :)
Thanks to the both of you for your help. I will go back to using the original library.
Thank you @burnside for the nice solution. I was trying to get the real time update hooked up to a websocket and found as part of of the initial rendering that candlestickSeires.update(lastCandle)
was getting called with an empty object, which it didn't like.
Here was my solution:
useEffect(() => {
if (!(lastCandle && Object.keys(lastCandle).length === 0 && lastCandle.constructor === Object)) {
candlestickSeries.update(lastCandle);
}
}, [lastCandle]);
Also found that adding
return () => chart.remove()
after
candlestickSeries.setData(initCandles);
eliminated an initial rendering of a blank chart.
I'm actually having another issue now, which is that when I try to add two chart components to the same page, only the second stays rendered. It also seems like the second one is hooked up to the websocket from the first one. Can anybody see where I might be going wrong:
const CandleChart = ({ market, symbol, decimals }) => {
// https://github.com/tradingview/lightweight-charts/blob/master/docs/customization.md
const classes = useStyles();
const chartRef = useRef();
const [initCandles, setInitCandles] = useState([])
const [lastCandle, setLastCandle] = useState({})
const wsRef = useRef()
const theme = useTheme();
useEffect(() => {
const apiUrl = `http://localhost:8000/klines/${market}/${symbol}`
async function getInitCandles() {
const response = await fetch(apiUrl);
const data = await response.json();
setInitCandles(data);
};
getInitCandles();
}
, [market, symbol]);
useEffect(() => {
wsRef.current = new WebSocket(`ws://localhost:8000/ws/${market}/${symbol}`)
wsRef.current.onmessage = (event) => {
console.log(market, event.data)
setLastCandle(JSON.parse(event.data))
};
return () => wsRef.current.close()
}
, [market, symbol]);
useEffect(() => {
chart = createChart(chartRef.current, {
width: chartRef.current.offsetWidth,
height: HEIGHT,
alignLabels: true,
timeScale: {
rightOffset: 0,
barSpacing: 15,
fixLeftEdge: false,
lockVisibleTimeRangeOnResize: true,
rightBarStaysOnScroll: true,
borderVisible: false,
visible: true,
timeVisible: true,
secondsVisible: true
},
rightPriceScale: {
scaleMargins: {
top: 0.3,
bottom: 0.25,
},
borderVisible: false,
},
priceScale: {
autoScale: true,
},
grid: {
vertLines: {
color: theme.palette.action.secondary,
style: 4
},
horzLines: {
color: theme.palette.action.secondary,
style: 4
},
},
layout: {
fontFamily: theme.typography.fontFamily,
backgroundColor: theme.palette.background.paper,
textColor: theme.palette.text.secondary
}
});
candlestickSeries = chart.addCandlestickSeries({
priceScaleId: 'right',
upColor: theme.palette.success.main,
downColor: theme.palette.error.main,
borderVisible: false,
wickVisible: true,
priceFormat: {
type: 'custom',
minMove: '0.00000001',
formatter: (price) => {
return parseFloat(price).toFixed(decimals);
}
},
});
candlestickSeries.setData(initCandles);
return () => chart.remove()
}, [initCandles, decimals]);
useEffect(() => {
if (!(lastCandle && Object.keys(lastCandle).length === 0 && lastCandle.constructor === Object)) {
candlestickSeries.update(lastCandle);
}
}, [lastCandle]);
useEffect(() => {
const handler = () => {
chart.resize(chartRef.current.offsetWidth, HEIGHT);
};
window.addEventListener('resize', handler);
return () => {
window.removeEventListener('resize', handler);
};
}, []);
return (
<div ref={chartRef} id={`${symbol}${market}chart`} style={{ 'position': 'relative', 'width': '100%' }}></div>
);
};
CandleChart.propTypes = {
market: PropTypes.string,
symbol: PropTypes.string,
decimals: PropTypes.number,
};
export default CandleChart;
@CalebEverett - When reading your description of your issue I thought you might have an overlapping div id, but I see in your code you caught the div id issue. I had a similar problem displaying multiple charts and used this to do the div id's randomly:
const DIVID = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10);
That's all I can think of off the top of my head though. I haven't tried to hook up separate websockets per chart.
@burnside Thanks so much - Ok, I'll give that a shot. I think it's close. When I'm console.logging the separate streams in the websocket useEffect, they are both showing up, so seems likely a reference issue.
@burnside I think I got is sorted. The global chart and candlestickSeries variables were the issue. I moved those down inside the CandleChart, using useRef for good measure and things seem to be working.
here's the modified component in case it helps somebody - thanks again for getting this going!:
import { Card, CardContent, Grid } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import { useTheme } from '@material-ui/core/styles'
import React, { useEffect, useRef, useState } from 'react';
import { createChart } from 'lightweight-charts';
import PropTypes from 'prop-types';
const HEIGHT = 300;
const useStyles = makeStyles({
root: {
width: "100%",
}
});
const CandleChart = ({ market, symbol, decimals }) => {
// https://github.com/tradingview/lightweight-charts/blob/master/docs/customization.md
const classes = useStyles();
const elRef = useRef();
const chartRef = useRef()
const candlestickSeriesRef = useRef()
const [initCandles, setInitCandles] = useState([])
const [lastCandle, setLastCandle] = useState({})
const theme = useTheme();
useEffect(() => {
const apiUrl = `http://localhost:8000/klines/${market}/${symbol}`
async function getInitCandles() {
const response = await fetch(apiUrl);
const data = await response.json();
setInitCandles(data);
};
getInitCandles();
}
, [market, symbol]);
useEffect(() => {
let ws = new WebSocket(`ws://localhost:8000/ws/${market}/${symbol}`)
ws.onmessage = (event) => {
setLastCandle(JSON.parse(event.data))
};
return () => ws.close()
}
, [market, symbol]);
useEffect(() => {
chartRef.current = createChart(elRef.current, {
width: elRef.current.offsetWidth,
height: HEIGHT,
alignLabels: true,
timeScale: {
rightOffset: 0,
barSpacing: 15,
fixLeftEdge: false,
lockVisibleTimeRangeOnResize: true,
rightBarStaysOnScroll: true,
borderVisible: false,
visible: true,
timeVisible: true,
secondsVisible: true
},
rightPriceScale: {
scaleMargins: {
top: 0.3,
bottom: 0.25,
},
borderVisible: false,
},
priceScale: {
autoScale: true,
},
grid: {
vertLines: {
color: theme.palette.action.secondary,
style: 4
},
horzLines: {
color: theme.palette.action.secondary,
style: 4
},
},
layout: {
fontFamily: theme.typography.fontFamily,
backgroundColor: theme.palette.background.paper,
textColor: theme.palette.text.secondary
}
});
candlestickSeriesRef.current = chartRef.current.addCandlestickSeries({
priceScaleId: 'right',
upColor: theme.palette.success.main,
downColor: theme.palette.error.main,
borderVisible: false,
wickVisible: true,
priceFormat: {
type: 'custom',
minMove: '0.00000001',
formatter: (price) => {
return parseFloat(price).toFixed(decimals);
}
},
});
candlestickSeriesRef.current.setData(initCandles);
return () => chartRef.current.remove()
}, [initCandles]);
useEffect(() => {
if (!(lastCandle && Object.keys(lastCandle).length === 0 && lastCandle.constructor === Object)) {
candlestickSeriesRef.current.update(lastCandle);
}
}, [lastCandle]);
useEffect(() => {
const handler = () => {
chartRef.current.resize(elRef.current.offsetWidth, HEIGHT);
};
window.addEventListener('resize', handler);
return () => {
window.removeEventListener('resize', handler);
};
}, []);
return (
<Grid item xs={12} lg={6}>
<Card className={classes.root}>
<CardContent>
<Typography variant="h5" color="textSecondary">
{symbol} {market}
</Typography>
<div ref={elRef} style={{ 'position': 'relative', 'width': '100%' }}></div>
</CardContent>
</Card>
</Grid>
);
};
CandleChart.propTypes = {
market: PropTypes.string,
symbol: PropTypes.string,
decimals: PropTypes.number,
};
export default CandleChart;
Thank you so much for your work hard sir. I'm currently using your lib to create my product. But i cannot found any function like series.update() to update realtime series data. So could you please help me to do this and update your repo