Spaces:
No application file
No application file
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Castor & Soybean Price Forecast Dashboard</title> | |
| <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: #f5f7fa; | |
| padding: 20px; | |
| } | |
| .main-container { | |
| max-width: 1600px; | |
| margin: 0 auto; | |
| } | |
| .header { | |
| background: white; | |
| padding: 30px; | |
| border-radius: 12px; | |
| margin-bottom: 20px; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| } | |
| .header h1 { | |
| color: #1a3a52; | |
| font-size: 2em; | |
| margin-bottom: 10px; | |
| } | |
| .header p { | |
| color: #666; | |
| font-size: 1em; | |
| } | |
| .top-section { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 20px; | |
| margin-bottom: 20px; | |
| } | |
| .left-panel { | |
| background: white; | |
| padding: 25px; | |
| border-radius: 12px; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| height: fit-content; | |
| } | |
| .commodity-section { | |
| margin-bottom: 25px; | |
| } | |
| .commodity-section label { | |
| display: block; | |
| font-size: 1em; | |
| font-weight: 600; | |
| color: #1a3a52; | |
| margin-bottom: 10px; | |
| } | |
| .commodity-select { | |
| width: 100%; | |
| padding: 12px; | |
| border: 2px solid #e0e4e8; | |
| border-radius: 8px; | |
| font-size: 1em; | |
| background: white; | |
| cursor: pointer; | |
| transition: border-color 0.3s; | |
| } | |
| .commodity-select:hover, | |
| .commodity-select:focus { | |
| border-color: #4CAF50; | |
| outline: none; | |
| } | |
| .time-range { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 10px; | |
| margin-bottom: 25px; | |
| } | |
| .time-btn { | |
| padding: 10px; | |
| border: 2px solid #e0e4e8; | |
| background: white; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-size: 0.95em; | |
| font-weight: 600; | |
| color: #666; | |
| transition: all 0.3s; | |
| } | |
| .time-btn:hover { | |
| border-color: #4CAF50; | |
| color: #4CAF50; | |
| } | |
| .time-btn.active { | |
| background: #4CAF50; | |
| color: white; | |
| border-color: #4CAF50; | |
| } | |
| .date-inputs { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 10px; | |
| margin-bottom: 15px; | |
| } | |
| .date-input-group { | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .date-input-group label { | |
| font-size: 0.85em; | |
| font-weight: 600; | |
| color: #666; | |
| margin-bottom: 5px; | |
| } | |
| .date-input-group input { | |
| padding: 10px; | |
| border: 2px solid #e0e4e8; | |
| border-radius: 8px; | |
| font-size: 0.95em; | |
| } | |
| .date-input-group input:focus { | |
| outline: none; | |
| border-color: #4CAF50; | |
| } | |
| .button-group { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 10px; | |
| } | |
| button { | |
| padding: 12px; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 0.95em; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| } | |
| .btn-load { | |
| background: #4CAF50; | |
| color: white; | |
| } | |
| .btn-load:hover { | |
| background: #45a049; | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3); | |
| } | |
| .btn-clear { | |
| background: #f0f0f0; | |
| color: #666; | |
| } | |
| .btn-clear:hover { | |
| background: #e0e0e0; | |
| } | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: 1fr; | |
| gap: 12px; | |
| } | |
| .stat-box { | |
| background: #f8f9fa; | |
| padding: 15px; | |
| border-radius: 8px; | |
| border-left: 4px solid #4CAF50; | |
| } | |
| .stat-label { | |
| font-size: 0.85em; | |
| color: #666; | |
| margin-bottom: 5px; | |
| } | |
| .stat-value { | |
| font-size: 1.4em; | |
| font-weight: 700; | |
| color: #1a3a52; | |
| } | |
| .right-panel { | |
| background: white; | |
| padding: 25px; | |
| border-radius: 12px; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| } | |
| .chart-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 15px; | |
| } | |
| .chart-title { | |
| font-size: 1.2em; | |
| font-weight: 600; | |
| color: #1a3a52; | |
| } | |
| .chart-buttons { | |
| display: flex; | |
| gap: 8px; | |
| } | |
| .chart-btn { | |
| padding: 6px 12px; | |
| background: #f0f0f0; | |
| border: 1px solid #ddd; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| font-size: 0.9em; | |
| font-weight: 600; | |
| color: #666; | |
| transition: all 0.3s; | |
| } | |
| .chart-btn.active { | |
| background: #4CAF50; | |
| color: white; | |
| border-color: #4CAF50; | |
| } | |
| .chart-btn:hover { | |
| border-color: #4CAF50; | |
| } | |
| .chart-container { | |
| width: 100%; | |
| height: 500px; | |
| border-radius: 8px; | |
| overflow: hidden; | |
| } | |
| #forecastChart { | |
| width: 100% ; | |
| height: 100% ; | |
| } | |
| .bottom-section { | |
| background: white; | |
| padding: 25px; | |
| border-radius: 12px; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| margin-top: 20px; | |
| } | |
| .tab-buttons { | |
| display: flex; | |
| gap: 10px; | |
| margin-bottom: 20px; | |
| border-bottom: 2px solid #e0e4e8; | |
| flex-wrap: wrap; | |
| } | |
| .tab-btn { | |
| padding: 12px 20px; | |
| background: none; | |
| border: none; | |
| border-bottom: 3px solid transparent; | |
| cursor: pointer; | |
| font-size: 0.95em; | |
| font-weight: 600; | |
| color: #666; | |
| transition: all 0.3s; | |
| margin-bottom: -2px; | |
| } | |
| .tab-btn:hover { | |
| color: #4CAF50; | |
| } | |
| .tab-btn.active { | |
| color: #4CAF50; | |
| border-bottom-color: #4CAF50; | |
| } | |
| .tab-content { | |
| display: none; | |
| } | |
| .tab-content.active { | |
| display: block; | |
| } | |
| .comparison-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 20px; | |
| } | |
| #arimaChart, #lstmChart { | |
| width: 100% ; | |
| height: 400px ; | |
| } | |
| .message { | |
| padding: 15px; | |
| border-radius: 8px; | |
| margin-bottom: 15px; | |
| display: none; | |
| } | |
| .message.error { | |
| background: #fee; | |
| color: #c33; | |
| border-left: 4px solid #c33; | |
| display: block; | |
| } | |
| .message.success { | |
| background: #efe; | |
| color: #3c3; | |
| border-left: 4px solid #3c3; | |
| display: block; | |
| } | |
| .loading { | |
| text-align: center; | |
| padding: 40px; | |
| color: #999; | |
| font-size: 1em; | |
| } | |
| @media (max-width: 1200px) { | |
| .top-section { | |
| grid-template-columns: 1fr; | |
| } | |
| .chart-container { | |
| height: 400px; | |
| } | |
| .comparison-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="main-container"> | |
| <div class="header"> | |
| <h1>📊 Castor & Soybean Price Forecast</h1> | |
| <p>Real-time API-powered forecasting with ARIMA & LSTM Models</p> | |
| </div> | |
| <div id="message" class="message"></div> | |
| <div class="top-section"> | |
| <!-- Left Panel: Controls --> | |
| <div class="left-panel"> | |
| <div class="commodity-section"> | |
| <label>Select Commodity</label> | |
| <select id="product" class="commodity-select"> | |
| <option value="Castor">Castor</option> | |
| <option value="Soybean">Soybean</option> | |
| </select> | |
| </div> | |
| <div class="commodity-section"> | |
| <label style="display: block; margin-bottom: 10px;">Forecast Period</label> | |
| <div class="time-range"> | |
| <button class="time-btn" onclick="setTimeRange(7)">7-Day</button> | |
| <button class="time-btn active" onclick="setTimeRange(15)">15-Day</button> | |
| <button class="time-btn" onclick="setTimeRange(30)">30-Day</button> | |
| </div> | |
| </div> | |
| <div class="commodity-section"> | |
| <label>Custom Date Range</label> | |
| <div class="date-inputs"> | |
| <div class="date-input-group"> | |
| <label style="margin-bottom: 5px; font-size: 0.85em;">From</label> | |
| <input type="date" id="startDate" value="2025-12-01"> | |
| </div> | |
| <div class="date-input-group"> | |
| <label style="margin-bottom: 5px; font-size: 0.85em;">To</label> | |
| <input type="date" id="endDate" value="2025-12-15"> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="button-group"> | |
| <button class="btn-load" onclick="loadForecast()">📈 Load Forecast</button> | |
| <button class="btn-clear" onclick="clearCharts()">🔄 Clear</button> | |
| </div> | |
| <div class="commodity-section" style="margin-top: 25px; border-top: 2px solid #e0e4e8; padding-top: 25px;"> | |
| <label>Price Statistics</label> | |
| <div class="stats-grid" id="stats"> | |
| <div class="stat-box"> | |
| <div class="stat-label">Current Price</div> | |
| <div class="stat-value">-</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-label">Forecast Average</div> | |
| <div class="stat-value">-</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-label">Min - Max</div> | |
| <div class="stat-value">-</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-label">Days Forecasted</div> | |
| <div class="stat-value">-</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right Panel: Main Chart --> | |
| <div class="right-panel"> | |
| <div class="chart-header"> | |
| <div class="chart-title">Price Forecast</div> | |
| <div class="chart-buttons"> | |
| <button class="chart-btn active" onclick="switchChartType('line')">Line</button> | |
| <button class="chart-btn" onclick="switchChartType('candle')">Candle</button> | |
| </div> | |
| </div> | |
| <div class="chart-container"> | |
| <div id="forecastChart" class="loading">Select commodity and click "Load Forecast"</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Bottom Section: Model Comparison --> | |
| <div class="bottom-section"> | |
| <div class="tab-buttons"> | |
| <button class="tab-btn active" onclick="switchTab('combined')">📊 Combined View</button> | |
| <button class="tab-btn" onclick="switchTab('arima')">📈 ARIMA Model</button> | |
| <button class="tab-btn" onclick="switchTab('lstm')">🧠 LSTM Model</button> | |
| <button class="tab-btn" onclick="switchTab('comparison')">🔄 Model Comparison</button> | |
| </div> | |
| <div id="combined" class="tab-content active"> | |
| <div id="combinedChart" class="loading">Load forecast to see chart...</div> | |
| </div> | |
| <div id="arima" class="tab-content"> | |
| <div id="arimaChart" class="loading">Load forecast to see chart...</div> | |
| </div> | |
| <div id="lstm" class="tab-content"> | |
| <div id="lstmChart" class="loading">Load forecast to see chart...</div> | |
| </div> | |
| <div id="comparison" class="tab-content"> | |
| <div id="comparisonChart" class="loading">Load forecast to see chart...</div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const API_KEY = 'castor_d167aa169b5e4219a66779e45fbaaefe'; | |
| const API_URL = 'http://127.0.0.1:5000'; | |
| let forecastData = null; | |
| let chartType = 'line'; | |
| function showMessage(text, type = 'info') { | |
| const msgEl = document.getElementById('message'); | |
| msgEl.className = 'message'; | |
| if (text) msgEl.classList.add(type); | |
| msgEl.textContent = text; | |
| } | |
| function setTimeRange(days) { | |
| // Update button active state | |
| document.querySelectorAll('.time-btn').forEach(btn => btn.classList.remove('active')); | |
| event.target.classList.add('active'); | |
| // Calculate dates | |
| const today = new Date('2025-12-01'); | |
| const start = today.toISOString().split('T')[0]; | |
| const end = new Date(today.getTime() + days * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; | |
| document.getElementById('startDate').value = start; | |
| document.getElementById('endDate').value = end; | |
| } | |
| function switchChartType(type) { | |
| chartType = type; | |
| document.querySelectorAll('.chart-btn').forEach(btn => btn.classList.remove('active')); | |
| event.target.classList.add('active'); | |
| if (forecastData) { | |
| updateMainChart(); | |
| } | |
| } | |
| async function loadForecast() { | |
| const startDate = document.getElementById('startDate').value; | |
| const endDate = document.getElementById('endDate').value; | |
| const product = document.getElementById('product').value; | |
| if (!startDate || !endDate) { | |
| showMessage('Please select both start and end dates', 'error'); | |
| return; | |
| } | |
| showMessage('Loading forecast data from API...', 'info'); | |
| try { | |
| const url = `${API_URL}/api/forecast?api_key=${API_KEY}&start_date=${startDate}&end_date=${endDate}&product=${product}`; | |
| const response = await fetch(url); | |
| if (!response.ok) { | |
| throw new Error(`API error: ${response.statusText}`); | |
| } | |
| forecastData = await response.json(); | |
| if (forecastData.status !== 'success') { | |
| throw new Error(forecastData.message || 'Unknown error'); | |
| } | |
| showMessage(`✓ Successfully loaded ${forecastData.forecast.length} days of forecast data`, 'success'); | |
| updateAllCharts(); | |
| updateStats(); | |
| } catch (error) { | |
| showMessage(`Error: ${error.message}. Ensure API is running at ${API_URL}`, 'error'); | |
| console.error('Fetch error:', error); | |
| } | |
| } | |
| function updateStats() { | |
| if (!forecastData || !forecastData.forecast) return; | |
| const forecast = forecastData.forecast; | |
| const prices = forecast.map(d => d.average_price); | |
| const stats = { | |
| current: forecastData.last_known_price, | |
| average: (prices.reduce((a, b) => a + b, 0) / prices.length).toFixed(2), | |
| min: Math.min(...prices).toFixed(2), | |
| max: Math.max(...prices).toFixed(2), | |
| days: forecast.length | |
| }; | |
| const statsHtml = ` | |
| <div class="stat-box"> | |
| <div class="stat-label">Current Price</div> | |
| <div class="stat-value">₹${stats.current}</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-label">Forecast Average</div> | |
| <div class="stat-value">₹${stats.average}</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-label">Min - Max</div> | |
| <div class="stat-value">₹${stats.min} - ₹${stats.max}</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-label">Days Forecasted</div> | |
| <div class="stat-value">${stats.days}</div> | |
| </div> | |
| `; | |
| document.getElementById('stats').innerHTML = statsHtml; | |
| } | |
| function updateMainChart() { | |
| if (!forecastData || !forecastData.forecast) return; | |
| const historical = forecastData.historical || []; | |
| const forecast = forecastData.forecast; | |
| // Combine historical and forecast dates | |
| const historicalDates = historical.map(d => d.date); | |
| const historicalPrices = historical.map(d => d.actual_price); | |
| const forecastDates = forecast.map(d => d.date); | |
| const avgPrices = forecast.map(d => d.average_price); | |
| const arimaPrices = forecast.map(d => d.arima_price); | |
| const lstmPrices = forecast.map(d => d.lstm_price); | |
| if (chartType === 'line') { | |
| // Historical trace (actual prices) | |
| const traceHistorical = { | |
| x: historicalDates, | |
| y: historicalPrices, | |
| type: 'scatter', | |
| mode: 'lines+markers', | |
| name: 'Actual', | |
| line: { color: '#FF9800', width: 3 }, | |
| marker: { size: 4, color: '#FF9800' } | |
| }; | |
| // Forecast trace (predicted prices) | |
| const traceForecast = { | |
| x: forecastDates, | |
| y: avgPrices, | |
| type: 'scatter', | |
| mode: 'lines+markers', | |
| name: 'Predicted', | |
| line: { color: '#4CAF50', width: 3 }, | |
| marker: { size: 5, color: '#4CAF50' }, | |
| fill: 'tozeroy', | |
| fillcolor: 'rgba(76, 175, 80, 0.1)' | |
| }; | |
| // Combine all dates for x-axis | |
| const allDates = [...historicalDates, ...forecastDates]; | |
| const layout = { | |
| title: 'Price Forecast - Actual vs Predicted', | |
| xaxis: { title: 'Date', gridcolor: '#e0e4e8' }, | |
| yaxis: { title: 'Price (₹)', gridcolor: '#e0e4e8' }, | |
| hovermode: 'x unified', | |
| plot_bgcolor: 'white', | |
| paper_bgcolor: 'white', | |
| margin: { l: 60, r: 60, t: 50, b: 50 }, | |
| shapes: [ | |
| { | |
| type: 'line', | |
| x0: historicalDates[historicalDates.length - 1], | |
| x1: historicalDates[historicalDates.length - 1], | |
| y0: 'paper', | |
| y1: 0.95, | |
| line: { | |
| color: '#999', | |
| width: 2, | |
| dash: 'dash' | |
| } | |
| } | |
| ] | |
| }; | |
| Plotly.newPlot('forecastChart', [traceHistorical, traceForecast], layout, { responsive: true }); | |
| } else { | |
| // Candlestick chart with actual and range | |
| const ohlcData = forecast.map((d, i) => ({ | |
| open: arimaPrices[i], | |
| high: Math.max(arimaPrices[i], lstmPrices[i]), | |
| low: Math.min(arimaPrices[i], lstmPrices[i]), | |
| close: lstmPrices[i], | |
| date: d.date | |
| })); | |
| const traceCandle = { | |
| x: ohlcData.map(d => d.date), | |
| open: ohlcData.map(d => d.open), | |
| high: ohlcData.map(d => d.high), | |
| low: ohlcData.map(d => d.low), | |
| close: ohlcData.map(d => d.close), | |
| type: 'candlestick', | |
| name: 'Predicted Range' | |
| }; | |
| const traceHistorical = { | |
| x: historicalDates, | |
| y: historicalPrices, | |
| type: 'scatter', | |
| mode: 'lines+markers', | |
| name: 'Actual', | |
| line: { color: '#FF9800', width: 2 }, | |
| marker: { size: 4, color: '#FF9800' } | |
| }; | |
| const layout = { | |
| title: 'Price Forecast - Actual vs Predicted (Candlestick)', | |
| xaxis: { title: 'Date', gridcolor: '#e0e4e8' }, | |
| yaxis: { title: 'Price (₹)', gridcolor: '#e0e4e8' }, | |
| plot_bgcolor: 'white', | |
| paper_bgcolor: 'white', | |
| margin: { l: 60, r: 60, t: 50, b: 50 } | |
| }; | |
| Plotly.newPlot('forecastChart', [traceHistorical, traceCandle], layout, { responsive: true }); | |
| } | |
| } | |
| function updateAllCharts() { | |
| updateMainChart(); | |
| updateCombinedChart(); | |
| updateArimaChart(); | |
| updateLstmChart(); | |
| updateComparisonChart(); | |
| } | |
| function updateCombinedChart() { | |
| if (!forecastData || !forecastData.forecast) return; | |
| const historical = forecastData.historical || []; | |
| const forecast = forecastData.forecast; | |
| const historicalDates = historical.map(d => d.date); | |
| const historicalPrices = historical.map(d => d.actual_price); | |
| const forecastDates = forecast.map(d => d.date); | |
| const avgPrices = forecast.map(d => d.average_price); | |
| const traceHistorical = { | |
| x: historicalDates, | |
| y: historicalPrices, | |
| type: 'scatter', | |
| mode: 'lines+markers', | |
| name: 'Actual', | |
| line: { color: '#FF9800', width: 2 }, | |
| marker: { size: 3, color: '#FF9800' } | |
| }; | |
| const traceForecast = { | |
| x: forecastDates, | |
| y: avgPrices, | |
| type: 'scatter', | |
| mode: 'lines+markers', | |
| name: 'Predicted', | |
| line: { color: '#4CAF50', width: 2 }, | |
| fill: 'tozeroy', | |
| fillcolor: 'rgba(76, 175, 80, 0.1)' | |
| }; | |
| const layout = { | |
| title: 'Combined Forecast (Actual + Predicted)', | |
| xaxis: { title: 'Date' }, | |
| yaxis: { title: 'Price (₹)' }, | |
| plot_bgcolor: 'white', | |
| hovermode: 'x unified' | |
| }; | |
| Plotly.newPlot('combinedChart', [traceHistorical, traceForecast], layout, { responsive: true }); | |
| } | |
| function updateArimaChart() { | |
| if (!forecastData || !forecastData.forecast) return; | |
| const forecast = forecastData.forecast; | |
| const dates = forecast.map(d => d.date); | |
| const arimaPrices = forecast.map(d => d.arima_price); | |
| const trace = { | |
| x: dates, | |
| y: arimaPrices, | |
| type: 'scatter', | |
| mode: 'lines+markers', | |
| name: 'ARIMA', | |
| line: { color: '#FF6B6B', width: 2 }, | |
| marker: { size: 4 } | |
| }; | |
| const layout = { | |
| title: 'ARIMA Model Forecast', | |
| xaxis: { title: 'Date' }, | |
| yaxis: { title: 'Price (₹)' }, | |
| plot_bgcolor: 'white', | |
| hovermode: 'x unified' | |
| }; | |
| Plotly.newPlot('arimaChart', [trace], layout, { responsive: true }); | |
| } | |
| function updateLstmChart() { | |
| if (!forecastData || !forecastData.forecast) return; | |
| const forecast = forecastData.forecast; | |
| const dates = forecast.map(d => d.date); | |
| const lstmPrices = forecast.map(d => d.lstm_price); | |
| const trace = { | |
| x: dates, | |
| y: lstmPrices, | |
| type: 'scatter', | |
| mode: 'lines+markers', | |
| name: 'LSTM', | |
| line: { color: '#4ECDC4', width: 2 }, | |
| marker: { size: 4 } | |
| }; | |
| const layout = { | |
| title: 'LSTM Model Forecast', | |
| xaxis: { title: 'Date' }, | |
| yaxis: { title: 'Price (₹)' }, | |
| plot_bgcolor: 'white', | |
| hovermode: 'x unified' | |
| }; | |
| Plotly.newPlot('lstmChart', [trace], layout, { responsive: true }); | |
| } | |
| function updateComparisonChart() { | |
| if (!forecastData || !forecastData.forecast) return; | |
| const forecast = forecastData.forecast; | |
| const dates = forecast.map(d => d.date); | |
| const arimaPrices = forecast.map(d => d.arima_price); | |
| const lstmPrices = forecast.map(d => d.lstm_price); | |
| const avgPrices = forecast.map(d => d.average_price); | |
| const traces = [ | |
| { x: dates, y: arimaPrices, type: 'scatter', mode: 'lines', name: 'ARIMA', line: { color: '#FF6B6B', width: 2 } }, | |
| { x: dates, y: lstmPrices, type: 'scatter', mode: 'lines', name: 'LSTM', line: { color: '#4ECDC4', width: 2 } }, | |
| { x: dates, y: avgPrices, type: 'scatter', mode: 'lines', name: 'Average', line: { color: '#4CAF50', width: 3, dash: 'dash' } } | |
| ]; | |
| const layout = { | |
| title: 'Model Comparison', | |
| xaxis: { title: 'Date' }, | |
| yaxis: { title: 'Price (₹)' }, | |
| plot_bgcolor: 'white', | |
| hovermode: 'x unified' | |
| }; | |
| Plotly.newPlot('comparisonChart', traces, layout, { responsive: true }); | |
| } | |
| function switchTab(tabName) { | |
| document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active')); | |
| document.querySelectorAll('.tab-btn').forEach(el => el.classList.remove('active')); | |
| document.getElementById(tabName).classList.add('active'); | |
| event.target.classList.add('active'); | |
| setTimeout(() => { | |
| if (tabName === 'combined') Plotly.redraw('combinedChart'); | |
| else if (tabName === 'arima') Plotly.redraw('arimaChart'); | |
| else if (tabName === 'lstm') Plotly.redraw('lstmChart'); | |
| else if (tabName === 'comparison') Plotly.redraw('comparisonChart'); | |
| }, 100); | |
| } | |
| function clearCharts() { | |
| forecastData = null; | |
| document.getElementById('stats').innerHTML = ` | |
| <div class="stat-box"><div class="stat-label">Current Price</div><div class="stat-value">-</div></div> | |
| <div class="stat-box"><div class="stat-label">Forecast Average</div><div class="stat-value">-</div></div> | |
| <div class="stat-box"><div class="stat-label">Min - Max</div><div class="stat-value">-</div></div> | |
| <div class="stat-box"><div class="stat-label">Days Forecasted</div><div class="stat-value">-</div></div> | |
| `; | |
| ['forecastChart', 'combinedChart', 'arimaChart', 'lstmChart', 'comparisonChart'].forEach(id => { | |
| document.getElementById(id).innerHTML = '<div class="loading">Load forecast to see chart...</div>'; | |
| }); | |
| showMessage(''); | |
| } | |
| // Initialize on load | |
| window.addEventListener('load', () => { | |
| setTimeRange(15); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |