-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore: update simple chart with real data #13
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,15 +1,162 @@ | ||||||
import React from 'react'; | ||||||
import { cn } from '@/lib/utils'; | ||||||
import React, { useEffect, useRef, useState } from "react"; | ||||||
import { cn } from "@/lib/utils"; | ||||||
import { useMarketWebSocket } from "@/hooks/websocket"; | ||||||
import { | ||||||
createChart, | ||||||
IChartApi, | ||||||
UTCTimestamp, | ||||||
SingleValueData, | ||||||
BaselineSeries, | ||||||
} from "lightweight-charts"; | ||||||
|
||||||
interface ChartProps { | ||||||
className?: string; | ||||||
} | ||||||
|
||||||
interface ChartData { | ||||||
time: UTCTimestamp; | ||||||
value: number; | ||||||
} | ||||||
|
||||||
export const Chart: React.FC<ChartProps> = ({ className }) => { | ||||||
const chartContainerRef = useRef<HTMLDivElement>(null); | ||||||
const chartRef = useRef<IChartApi | null>(null); | ||||||
const seriesRef = useRef<any | null>(null); | ||||||
const [currentPrice, setCurrentPrice] = useState<number | null>(null); | ||||||
const [currentTime, setCurrentTime] = useState<string | null>(null); | ||||||
const [priceHistory, setPriceHistory] = useState<ChartData[]>([]); | ||||||
|
||||||
const { isConnected } = useMarketWebSocket("R_100", { | ||||||
onConnect: () => console.log("Market WebSocket Connected in Chart"), | ||||||
onError: (err) => console.log("Market WebSocket Error in Chart:", err), | ||||||
onPrice: (price) => { | ||||||
if (price?.ask) { | ||||||
const timestamp = new Date(price.timestamp); | ||||||
const newPrice: ChartData = { | ||||||
time: Math.floor(timestamp.getTime() / 1000) as UTCTimestamp, | ||||||
value: price.ask, | ||||||
}; | ||||||
setPriceHistory((prev) => [...prev, newPrice]); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (performance): Consider implementing a limit on price history to prevent unbounded growth The price history array will grow indefinitely. Consider implementing a maximum length and removing older entries. Suggested implementation: const MAX_PRICE_HISTORY = 1000; // Keep last 1000 price points
const { isConnected } = useMarketWebSocket("R_100", { setPriceHistory((prev) => {
const updatedHistory = [...prev, newPrice];
return updatedHistory.length > MAX_PRICE_HISTORY
? updatedHistory.slice(-MAX_PRICE_HISTORY)
: updatedHistory;
}); |
||||||
setCurrentPrice(price.ask); | ||||||
setCurrentTime(timestamp.toLocaleString()); | ||||||
|
||||||
if (seriesRef.current) { | ||||||
seriesRef.current.update(newPrice); | ||||||
} | ||||||
} | ||||||
}, | ||||||
}); | ||||||
|
||||||
useEffect(() => { | ||||||
if (!chartContainerRef.current) return; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (code-quality): Use block braces for ifs, whiles, etc. (
Suggested change
ExplanationIt is recommended to always use braces and create explicit statement blocks.Using the allowed syntax to just write a single statement can lead to very confusing |
||||||
|
||||||
// Create chart | ||||||
const chart = createChart(chartContainerRef.current, { | ||||||
layout: { | ||||||
background: { color: "white" }, | ||||||
textColor: "black", | ||||||
}, | ||||||
grid: { | ||||||
vertLines: { color: "#f0f0f0" }, | ||||||
horzLines: { color: "#f0f0f0" }, | ||||||
}, | ||||||
rightPriceScale: { | ||||||
borderVisible: false, | ||||||
}, | ||||||
timeScale: { | ||||||
borderVisible: false, | ||||||
timeVisible: true, | ||||||
secondsVisible: true, | ||||||
}, | ||||||
crosshair: { | ||||||
vertLine: { | ||||||
labelBackgroundColor: "#404040", | ||||||
}, | ||||||
horzLine: { | ||||||
labelBackgroundColor: "#404040", | ||||||
}, | ||||||
}, | ||||||
width: chartContainerRef.current.clientWidth, | ||||||
height: chartContainerRef.current.clientHeight, | ||||||
}); | ||||||
|
||||||
const baselineSeries = chart.addSeries(BaselineSeries, { | ||||||
// baseValue: { type: "price", price: undefined }, | ||||||
topLineColor: "rgba( 38, 166, 154, 1)", | ||||||
topFillColor1: "rgba( 38, 166, 154, 0.28)", | ||||||
topFillColor2: "rgba( 38, 166, 154, 0.05)", | ||||||
bottomLineColor: "rgba( 239, 83, 80, 1)", | ||||||
bottomFillColor1: "rgba( 239, 83, 80, 0.05)", | ||||||
bottomFillColor2: "rgba( 239, 83, 80, 0.28)", | ||||||
}); | ||||||
|
||||||
chartRef.current = chart; | ||||||
seriesRef.current = baselineSeries; | ||||||
|
||||||
// Subscribe to crosshair move to update the tooltip | ||||||
chart.subscribeCrosshairMove((param) => { | ||||||
if (param.time) { | ||||||
const data = param.seriesData.get(baselineSeries) as SingleValueData; | ||||||
if (data?.value) { | ||||||
setCurrentPrice(data.value); | ||||||
const timestamp = new Date((param.time as number) * 1000); | ||||||
setCurrentTime(timestamp.toLocaleString()); | ||||||
} | ||||||
} | ||||||
}); | ||||||
|
||||||
// Handle window resize | ||||||
const handleResize = () => { | ||||||
if (chartContainerRef.current && chartRef.current) { | ||||||
chartRef.current.applyOptions({ | ||||||
width: chartContainerRef.current.clientWidth, | ||||||
height: chartContainerRef.current.clientHeight, | ||||||
}); | ||||||
} | ||||||
}; | ||||||
|
||||||
window.addEventListener("resize", handleResize); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (performance): Consider debouncing the resize handler to improve performance Frequent resize events could impact performance. Consider using a debounced version of the handler. Suggested implementation: // Import at the top of the file
import debounce from 'lodash/debounce';
// Handle window resize with debouncing
const handleResize = debounce(() => { return () => {
window.removeEventListener("resize", handleResize);
handleResize.cancel(); // Cancel any pending debounced calls You'll need to:
|
||||||
|
||||||
// Cleanup | ||||||
return () => { | ||||||
window.removeEventListener("resize", handleResize); | ||||||
if (chartRef.current) { | ||||||
chartRef.current.remove(); | ||||||
} | ||||||
}; | ||||||
}, []); | ||||||
|
||||||
// Update data when price history changes | ||||||
useEffect(() => { | ||||||
if (seriesRef.current && priceHistory.length > 0) { | ||||||
seriesRef.current.setData(priceHistory); | ||||||
chartRef.current?.timeScale().fitContent(); | ||||||
} | ||||||
}, [priceHistory]); | ||||||
|
||||||
return ( | ||||||
<div className={cn("contents", className)}> | ||||||
<div className="flex-1 bg-gray-100 h-full w-full rounded-lg flex items-center justify-center"> | ||||||
Chart Component | ||||||
<div className={cn("flex flex-col flex-1", className)}> | ||||||
<div className="flex-1 bg-white w-full rounded-lg relative min-h-[400px]"> | ||||||
{currentPrice && currentTime && ( | ||||||
<div className="absolute top-4 left-4 bg-gray-100 p-2 rounded shadow-sm z-10"> | ||||||
<div className="text-sm font-medium">VOLATILITY 100 (1S) INDEX</div> | ||||||
<div className="text-lg font-bold">{currentPrice.toFixed(2)}</div> | ||||||
<div className="text-xs text-gray-600">{currentTime}</div> | ||||||
</div> | ||||||
)} | ||||||
<div | ||||||
ref={chartContainerRef} | ||||||
className="w-full h-full" | ||||||
data-testid="chart-container" | ||||||
/> | ||||||
{!isConnected && ( | ||||||
<div | ||||||
data-testid="ws-disconnected" | ||||||
className="text-center text-red-500" | ||||||
> | ||||||
Disconnected | ||||||
</div> | ||||||
)} | ||||||
</div> | ||||||
</div> | ||||||
); | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Chart Component | ||
|
||
The Chart component displays market data using a WebSocket connection. It is responsible for: | ||
|
||
- **Real-Time Price Updates:** Receiving and displaying live market prices. | ||
- **Connection Status:** Indicating whether the WebSocket connection is active or disconnected. | ||
- **Error Handling:** Displaying errors when the WebSocket encounters issues. | ||
- **Customization:** Accepting an optional `className` prop for styling. | ||
|
||
## Usage | ||
|
||
```tsx | ||
import { Chart } from "@/components/Chart"; | ||
|
||
function App() { | ||
return ( | ||
<div> | ||
<Chart className="custom-chart-class" /> | ||
</div> | ||
); | ||
} | ||
``` | ||
|
||
## Props | ||
|
||
| Prop | Type | Description | | ||
|-----------|--------|---------------------------------------| | ||
| className | string | Optional CSS class for the component. | | ||
|
||
## Implementation Details | ||
|
||
- The component uses the `useMarketWebSocket` hook with a hardcoded instrument ID of `"R_100"`. | ||
- WebSocket events such as connection, price updates, and errors are logged to the console. | ||
- The UI displays the current market price, a disconnected message when the connection is lost, and any error messages. | ||
|
||
## Future Enhancements | ||
|
||
- Integrate a visual charting library for dynamic price visualization. | ||
- Add configurable instrument IDs and callback functions via props. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (bug_risk): Consider implementing proper error handling for WebSocket errors
Instead of just logging to console, consider showing an error state to users and implementing reconnection logic.