Spaces:
Running
we need a frontend web ui which ties together the backend consisting of our insight scraping algorithms:
Browse filesimport asyncio
import aiohttp
import json
import time
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
from collections import defaultdict
import sqlite3
from textblob import TextBlob
import re
from urllib.parse import quote_plus
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@dataclass
class TrendData:
"""Data structure for trend information"""
keyword: str
source: str
volume: int
sentiment: float
timestamp: datetime
metadata: Dict
@dataclass
class MarketGap:
"""Data structure for identified market gaps"""
gap_description: str
evidence_score: float
demand_indicators: List[str]
supply_gaps: List[str]
related_keywords: List[str]
confidence_level: float
class APIClient:
"""Generic API client with rate limiting and error handling"""
def __init__(self, base_url: str, rate_limit: float = 1.0):
self.base_url = base_url
self.rate_limit = rate_limit
self.last_request = 0
self.session = None
async def __aenter__(self):
self.session = aiohttp.ClientSession()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
if self.session:
await self.session.close()
async def make_request(self, endpoint: str, params: Dict = None, headers: Dict = None):
"""Make rate-limited API request"""
# Rate limiting
now = time.time()
time_since_last = now - self.last_request
if time_since_last < self.rate_limit:
await asyncio.sleep(self.rate_limit - time_since_last)
try:
url = f"{self.base_url}/{endpoint.lstrip('/')}"
async with self.session.get(url, params=params, headers=headers) as response:
self.last_request = time.time()
if response.status == 200:
return await response.json()
else:
logger.warning(f"API request failed: {response.status}")
return None
except Exception as e:
logger.error(f"API request error: {e}")
return None
class TrendScraper:
"""Main class for scraping trend data from multiple sources"""
def __init__(self):
self.db_path = "market_trends.db"
self.initialize_database()
def initialize_database(self):
"""Initialize SQLite database for storing trend data"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# Create trends table
cursor.execute('''
CREATE TABLE IF NOT EXISTS trends (
id INTEGER PRIMARY KEY AUTOINCREMENT,
keyword TEXT,
source TEXT,
volume INTEGER,
sentiment REAL,
timestamp TEXT,
metadata TEXT
)
''')
# Create market_gaps table
cursor.execute('''
CREATE TABLE IF NOT EXISTS market_gaps (
id INTEGER PRIMARY KEY AUTOINCREMENT,
gap_description TEXT,
evidence_score REAL,
demand_indicators TEXT,
supply_gaps TEXT,
related_keywords TEXT,
confidence_level REAL,
created_at TEXT
)
''')
conn.commit()
conn.close()
async def scrape_reddit_trends(self, keywords: List[str]) -> List[TrendData]:
"""Scrape Reddit for trend data using pushshift API"""
trends = []
async with APIClient("https://api.pushshift.io/reddit/search", rate_limit=1.0) as client:
for keyword in keywords:
params = {
'q': keyword,
'subreddit': 'all',
'sort': 'desc',
'sort_type': 'created_utc',
'size': 100,
'after': int((datetime.now() - timedelta(days=30)).timestamp())
}
data = await client.make_request("submission", params)
if data and 'data' in data:
for post in data['data']:
sentiment = TextBlob(post.get('title', '') + ' ' + post.get('selftext', '')).sentiment.polarity
trends.append(TrendData(
keyword=keyword,
source='reddit',
volume=post.get('score', 0),
sentiment=sentiment,
timestamp=datetime.fromtimestamp(post.get('created_utc', 0)),
metadata={'subreddit': post.get('subreddit'), 'comments': post.get('num_comments', 0)}
))
return trends
async def scrape_twitter_trends(self, keywords: List[str]) -> List[TrendData]:
"""Simulate Twitter trend scraping (requires Twitter API credentials)"""
# This is a placeholder - you'll need to implement with actual Twitter API
trends = []
# Simulated data for demonstration
for keyword in keywords:
trends.append(TrendData(
keyword=keyword,
source='twitter',
volume=np.random.randint(100, 10000),
sentiment=np.random.uniform(-1, 1),
timestamp=datetime.now(),
metadata={'hashtags': f"#{keyword}", 'retweets': np.random.randint(10, 1000)}
))
return trends
async def scrape_google_trends(self, keywords: List[str]) -> List[TrendData]:
"""Scrape Google Trends data (requires pytrends library)"""
trends = []
try:
from pytrends.request import TrendReq
pytrends = TrendReq(hl='en-US', tz=360)
for keyword in keywords:
pytrends.build_payload([keyword], timeframe='today 3-m')
interest_over_time = pytrends.interest_over_time()
if not interest_over_time.empty:
for date, row in interest_over_time.iterrows():
trends.append(TrendData(
keyword=keyword,
source='google_trends',
volume=int(row[keyword]),
sentiment=0.0, # Google Trends doesn't provide sentiment
timestamp=date,
metadata={'isPartial': row.get('isPartial', False)}
))
except ImportError:
logger.warning("pytrends not installed. Skipping Google Trends scraping.")
return trends
async def scrape_github_trends(self, keywords: List[str]) -> List[TrendData]:
"""Scrape GitHub for repository trends"""
trends = []
async with APIClient("https://api.github.com", rate_limit=1.0) as client:
headers = {'Accept': 'application/vnd.github.v3+json'}
for keyword in keywords:
params = {
'q': keyword,
'sort': 'updated',
'order': 'desc',
'per_page': 100
}
data = await client.make_request("search/repositories", params, headers)
if data and 'items' in data:
for repo in data['items']:
trends.append(TrendData(
keyword=keyword,
source='github',
volume=repo.get('stargazers_count', 0),
sentiment=0.5, # Neutral sentiment for GitHub
timestamp=datetime.fromisoformat(repo.get('updated_at', '').replace('Z', '+00:00')),
metadata={
'language': repo.get('language'),
'forks': repo.get('forks_count', 0),
'issues': repo.get('open_issues_count', 0)
}
))
return trends
async def scrape_news_trends(self, keywords: List[str]) -> List[TrendData]:
"""Scrape news trends using NewsAPI (requires API key)"""
trends = []
# Placeholder for NewsAPI integration
# You'll need to register for a NewsAPI key at https://newsapi.org/
API_KEY = "YOUR_NEWSAPI_KEY" # Replace with actual key
if API_KEY != "YOUR_NEWSAPI_KEY":
async with APIClient("https://newsapi.org/v2", rate_limit=1.0) as client:
headers = {'X-API-Key': API_KEY}
for keyword in keywords:
params = {
'q': keyword,
'sortBy': 'popularity',
'pageSize': 100,
'from': (datetime.now() - timedelta(days=30)).isoformat()
}
data = await client.make_request("everything", params, headers)
if data and 'articles' in data:
for article in data['articles']:
sentiment = TextBlob(article.get('title', '') + ' ' + article.get('description', '')).sentiment.polarity
trends.append(TrendData(
keyword=keyword,
source='news',
volume=1, # News articles don't have volume metrics
sentiment=sentiment,
timestamp=datetime.fromisoformat(article.get('publishedAt', '').replace('Z', '
- README.md +7 -5
- index.html +676 -18
- prompts.txt +0 -0
|
@@ -1,10 +1,12 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
+
title: mga-basic-test
|
| 3 |
+
emoji: 🐳
|
| 4 |
+
colorFrom: yellow
|
| 5 |
+
colorTo: blue
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite
|
| 10 |
---
|
| 11 |
|
| 12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
@@ -1,19 +1,677 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>MarketGap - Market Intelligence Dashboard</title>
|
| 7 |
+
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
|
| 10 |
+
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
|
| 11 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 12 |
+
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
| 13 |
+
<style>
|
| 14 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
| 15 |
+
|
| 16 |
+
:root {
|
| 17 |
+
--primary: #6366f1;
|
| 18 |
+
--primary-dark: #4f46e5;
|
| 19 |
+
--secondary: #06b6d4;
|
| 20 |
+
--accent: #f59e0b;
|
| 21 |
+
--success: #10b981;
|
| 22 |
+
--warning: #f59e0b;
|
| 23 |
+
--danger: #ef4444;
|
| 24 |
+
--dark: #1e293b;
|
| 25 |
+
--light: #f8fafc;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
body {
|
| 29 |
+
font-family: 'Inter', sans-serif;
|
| 30 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 31 |
+
min-height: 100vh;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.glass-morphism {
|
| 35 |
+
background: rgba(255, 255, 255, 0.1);
|
| 36 |
+
backdrop-filter: blur(10px);
|
| 37 |
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 38 |
+
border-radius: 16px;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.gradient-text {
|
| 42 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 43 |
+
-webkit-background-clip: text;
|
| 44 |
+
-webkit-text-fill-color: transparent;
|
| 45 |
+
background-clip: text;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
.pulse-animation {
|
| 49 |
+
animation: pulse 2s infinite;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
@keyframes pulse {
|
| 53 |
+
0% { opacity: 1; }
|
| 54 |
+
50% { opacity: 0.5; }
|
| 55 |
+
100% { opacity: 1; }
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
.floating {
|
| 59 |
+
animation: floating 3s ease-in-out infinite;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
@keyframes floating {
|
| 63 |
+
0% { transform: translateY(0px); }
|
| 64 |
+
50% { transform: translateY(-10px); }
|
| 65 |
+
100% { transform: translateY(0px); }
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
.chart-container {
|
| 69 |
+
background: rgba(255, 255, 255, 0.95);
|
| 70 |
+
border-radius: 16px;
|
| 71 |
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
| 72 |
+
backdrop-filter: blur(4px);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.loading-spinner {
|
| 76 |
+
border: 3px solid #f3f3f3;
|
| 77 |
+
border-top: 3px solid var(--primary);
|
| 78 |
+
border-radius: 50%;
|
| 79 |
+
width: 40px;
|
| 80 |
+
height: 40px;
|
| 81 |
+
animation: spin 1s linear infinite;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
@keyframes spin {
|
| 85 |
+
0% { transform: rotate(0deg); }
|
| 86 |
+
100% { transform: rotate(360deg); }
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.metric-card {
|
| 90 |
+
transition: all 0.3s ease;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
.metric-card:hover {
|
| 94 |
+
transform: translateY(-5px);
|
| 95 |
+
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.opportunity-card {
|
| 99 |
+
transition: all 0.3s ease;
|
| 100 |
+
border-left: 4px solid var(--primary);
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.opportunity-card:hover {
|
| 104 |
+
transform: translateX(5px);
|
| 105 |
+
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
.btn-primary {
|
| 109 |
+
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
|
| 110 |
+
border: none;
|
| 111 |
+
color: white;
|
| 112 |
+
padding: 12px 24px;
|
| 113 |
+
border-radius: 8px;
|
| 114 |
+
font-weight: 600;
|
| 115 |
+
transition: all 0.3s ease;
|
| 116 |
+
cursor: pointer;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
.btn-primary:hover {
|
| 120 |
+
transform: translateY(-2px);
|
| 121 |
+
box-shadow: 0 8px 16px rgba(99, 102, 241, 0.3);
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
.btn-secondary {
|
| 125 |
+
background: rgba(255, 255, 255, 0.2);
|
| 126 |
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
| 127 |
+
color: white;
|
| 128 |
+
padding: 12px 24px;
|
| 129 |
+
border-radius: 8px;
|
| 130 |
+
font-weight: 600;
|
| 131 |
+
transition: all 0.3s ease;
|
| 132 |
+
cursor: pointer;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
.btn-secondary:hover {
|
| 136 |
+
background: rgba(255, 255, 255, 0.3);
|
| 137 |
+
transform: translateY(-2px);
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
.keyword-tag {
|
| 141 |
+
background: rgba(255, 255, 255, 0.2);
|
| 142 |
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
| 143 |
+
color: white;
|
| 144 |
+
padding: 4px 12px;
|
| 145 |
+
border-radius: 20px;
|
| 146 |
+
font-size: 14px;
|
| 147 |
+
margin: 2px;
|
| 148 |
+
display: inline-block;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
.trend-indicator {
|
| 152 |
+
display: inline-flex;
|
| 153 |
+
align-items: center;
|
| 154 |
+
gap: 4px;
|
| 155 |
+
font-size: 12px;
|
| 156 |
+
font-weight: 600;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
.trend-up { color: var(--success); }
|
| 160 |
+
.trend-down { color: var(--danger); }
|
| 161 |
+
.trend-neutral { color: var(--warning); }
|
| 162 |
+
|
| 163 |
+
.sentiment-positive { color: var(--success); }
|
| 164 |
+
.sentiment-negative { color: var(--danger); }
|
| 165 |
+
.sentiment-neutral { color: var(--warning); }
|
| 166 |
+
|
| 167 |
+
.animate-fade-in {
|
| 168 |
+
animation: fadeIn 0.6s ease-out;
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
@keyframes fadeIn {
|
| 172 |
+
from { opacity: 0; transform: translateY(20px); }
|
| 173 |
+
to { opacity: 1; transform: translateY(0); }
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
.scrollbar-hide {
|
| 177 |
+
-ms-overflow-style: none;
|
| 178 |
+
scrollbar-width: none;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
.scrollbar-hide::-webkit-scrollbar {
|
| 182 |
+
display: none;
|
| 183 |
+
}
|
| 184 |
+
</style>
|
| 185 |
+
</head>
|
| 186 |
+
<body>
|
| 187 |
+
<!-- Background Animation -->
|
| 188 |
+
<div id="vanta-bg" class="fixed inset-0 z-0"></div>
|
| 189 |
+
|
| 190 |
+
<!-- Navigation -->
|
| 191 |
+
<nav class="relative z-10 glass-morphism m-4 p-4 flex justify-between items-center animate-fade-in">
|
| 192 |
+
<div class="flex items-center gap-3">
|
| 193 |
+
<div class="w-10 h-10 bg-gradient-to-r from-indigo-500 to-purple-600 rounded-lg flex items-center justify-center">
|
| 194 |
+
<i data-feather="trending-up" class="text-white"></i>
|
| 195 |
+
</div>
|
| 196 |
+
<h1 class="text-xl font-bold text-white">MarketGap</h1>
|
| 197 |
+
</div>
|
| 198 |
+
<div class="flex items-center gap-4">
|
| 199 |
+
<button id="refresh-btn" class="btn-secondary">
|
| 200 |
+
<i data-feather="refresh-cw" class="w-4 h-4 inline mr-2"></i>Refresh
|
| 201 |
+
</button>
|
| 202 |
+
<button id="export-btn" class="btn-primary">
|
| 203 |
+
<i data-feather="download" class="w-4 h-4 inline mr-2"></i>Export
|
| 204 |
+
</button>
|
| 205 |
+
</div>
|
| 206 |
+
</nav>
|
| 207 |
+
|
| 208 |
+
<!-- Main Content -->
|
| 209 |
+
<div class="relative z-10 container mx-auto px-4 py-8">
|
| 210 |
+
<!-- Header Section -->
|
| 211 |
+
<div class="text-center mb-12 animate-fade-in" data-aos="fade-up">
|
| 212 |
+
<h2 class="text-5xl font-bold text-white mb-4 gradient-text">Market Intelligence Dashboard</h2>
|
| 213 |
+
<p class="text-xl text-white/80 max-w-2xl mx-auto">
|
| 214 |
+
Discover hidden market opportunities with AI-powered trend analysis across multiple platforms
|
| 215 |
+
</p>
|
| 216 |
+
</div>
|
| 217 |
+
|
| 218 |
+
<!-- Input Section -->
|
| 219 |
+
<div class="glass-morphism p-8 mb-12 animate-fade-in" data-aos="fade-up" data-aos-delay="200">
|
| 220 |
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
| 221 |
+
<div>
|
| 222 |
+
<label class="block text-white font-semibold mb-3">Keywords to Analyze</label>
|
| 223 |
+
<input
|
| 224 |
+
id="keywords-input"
|
| 225 |
+
type="text"
|
| 226 |
+
placeholder="e.g., AI productivity, remote work, sustainable packaging"
|
| 227 |
+
class="w-full px-4 py-3 rounded-lg bg-white/20 border border-white/30 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
|
| 228 |
+
>
|
| 229 |
+
<div class="mt-2 flex flex-wrap gap-2" id="keyword-tags">
|
| 230 |
+
<span class="keyword-tag">AI productivity</span>
|
| 231 |
+
<span class="keyword-tag">remote work</span>
|
| 232 |
+
<span class="keyword-tag">sustainable packaging</span>
|
| 233 |
+
</div>
|
| 234 |
+
</div>
|
| 235 |
+
<div>
|
| 236 |
+
<label class="block text-white font-semibold mb-3">Analysis Options</label>
|
| 237 |
+
<div class="grid grid-cols-2 gap-4 mb-4">
|
| 238 |
+
<div>
|
| 239 |
+
<label class="block text-white/80 text-sm mb-1">Time Period</label>
|
| 240 |
+
<select id="time-period" class="w-full px-3 py-2 rounded-lg bg-white/20 border border-white/30 text-white">
|
| 241 |
+
<option value="7">7 days</option>
|
| 242 |
+
<option value="30" selected>30 days</option>
|
| 243 |
+
<option value="90">90 days</option>
|
| 244 |
+
<option value="365">1 year</option>
|
| 245 |
+
</select>
|
| 246 |
+
</div>
|
| 247 |
+
<div>
|
| 248 |
+
<label class="block text-white/80 text-sm mb-1">Data Sources</label>
|
| 249 |
+
<select id="data-sources" class="w-full px-3 py-2 rounded-lg bg-white/20 border border-white/30 text-white">
|
| 250 |
+
<option value="all" selected>All Sources</option>
|
| 251 |
+
<option value="reddit">Reddit</option>
|
| 252 |
+
<option value="twitter">Twitter</option>
|
| 253 |
+
<option value="github">GitHub</option>
|
| 254 |
+
<option value="news">News</option>
|
| 255 |
+
</select>
|
| 256 |
+
</div>
|
| 257 |
+
</div>
|
| 258 |
+
<div class="flex gap-3">
|
| 259 |
+
<button id="analyze-btn" class="btn-primary flex-1">
|
| 260 |
+
<i data-feather="search" class="w-4 h-4 inline mr-2"></i>Analyze Trends
|
| 261 |
+
</button>
|
| 262 |
+
<button id="quick-scan-btn" class="btn-secondary">
|
| 263 |
+
<i data-feather="zap" class="w-4 h-4 inline mr-2"></i>Quick Scan
|
| 264 |
+
</button>
|
| 265 |
+
</div>
|
| 266 |
+
</div>
|
| 267 |
+
</div>
|
| 268 |
+
</div>
|
| 269 |
+
|
| 270 |
+
<!-- Metrics Overview -->
|
| 271 |
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12 animate-fade-in" data-aos="fade-up" data-aos-delay="300">
|
| 272 |
+
<div class="metric-card glass-morphism p-6 text-center">
|
| 273 |
+
<div class="text-3xl font-bold text-white mb-2" id="total-data-points">0</div>
|
| 274 |
+
<div class="text-white/70">Data Points</div>
|
| 275 |
+
<div class="trend-indicator trend-up mt-2">
|
| 276 |
+
<i data-feather="trending-up" class="w-4 h-4"></i>
|
| 277 |
+
<span>+12.5%</span>
|
| 278 |
+
</div>
|
| 279 |
+
</div>
|
| 280 |
+
<div class="metric-card glass-morphism p-6 text-center">
|
| 281 |
+
<div class="text-3xl font-bold text-white mb-2" id="market-gaps">0</div>
|
| 282 |
+
<div class="text-white/70">Market Gaps</div>
|
| 283 |
+
<div class="trend-indicator trend-up mt-2">
|
| 284 |
+
<i data-feather="trending-up" class="w-4 h-4"></i>
|
| 285 |
+
<span>+8.3%</span>
|
| 286 |
+
</div>
|
| 287 |
+
</div>
|
| 288 |
+
<div class="metric-card glass-morphism p-6 text-center">
|
| 289 |
+
<div class="text-3xl font-bold text-white mb-2" id="avg-sentiment">0.0</div>
|
| 290 |
+
<div class="text-white/70">Avg Sentiment</div>
|
| 291 |
+
<div class="trend-indicator trend-neutral mt-2">
|
| 292 |
+
<i data-feather="minus" class="w-4 h-4"></i>
|
| 293 |
+
<span>Neutral</span>
|
| 294 |
+
</div>
|
| 295 |
+
</div>
|
| 296 |
+
<div class="metric-card glass-morphism p-6 text-center">
|
| 297 |
+
<div class="text-3xl font-bold text-white mb-2" id="confidence-score">0%</div>
|
| 298 |
+
<div class="text-white/70">Confidence</div>
|
| 299 |
+
<div class="trend-indicator trend-up mt-2">
|
| 300 |
+
<i data-feather="trending-up" class="w-4 h-4"></i>
|
| 301 |
+
<span>+15.2%</span>
|
| 302 |
+
</div>
|
| 303 |
+
</div>
|
| 304 |
+
</div>
|
| 305 |
+
|
| 306 |
+
<!-- Main Dashboard -->
|
| 307 |
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 animate-fade-in" data-aos="fade-up" data-aos-delay="400">
|
| 308 |
+
<!-- Left Column: Charts -->
|
| 309 |
+
<div class="lg:col-span-2 space-y-8">
|
| 310 |
+
<!-- Trend Timeline Chart -->
|
| 311 |
+
<div class="chart-container p-6">
|
| 312 |
+
<div class="flex justify-between items-center mb-4">
|
| 313 |
+
<h3 class="text-xl font-bold text-gray-800">Trend Timeline</h3>
|
| 314 |
+
<div class="flex gap-2">
|
| 315 |
+
<button class="btn-secondary text-sm px-3 py-1">
|
| 316 |
+
<i data-feather="bar-chart-2" class="w-3 h-3"></i>
|
| 317 |
+
</button>
|
| 318 |
+
<button class="btn-secondary text-sm px-3 py-1">
|
| 319 |
+
<i data-feather="pie-chart" class="w-3 h-3"></i>
|
| 320 |
+
</button>
|
| 321 |
+
</div>
|
| 322 |
+
</div>
|
| 323 |
+
<div id="trend-timeline-chart" style="height: 300px;"></div>
|
| 324 |
+
</div>
|
| 325 |
+
|
| 326 |
+
<!-- Source Distribution -->
|
| 327 |
+
<div class="chart-container p-6">
|
| 328 |
+
<h3 class="text-xl font-bold text-gray-800 mb-4">Data Source Distribution</h3>
|
| 329 |
+
<div id="source-distribution-chart" style="height: 250px;"></div>
|
| 330 |
+
</div>
|
| 331 |
+
|
| 332 |
+
<!-- Sentiment Heatmap -->
|
| 333 |
+
<div class="chart-container p-6">
|
| 334 |
+
<h3 class="text-xl font-bold text-gray-800 mb-4">Sentiment Analysis</h3>
|
| 335 |
+
<div id="sentiment-heatmap-chart" style="height: 300px;"></div>
|
| 336 |
+
</div>
|
| 337 |
+
</div>
|
| 338 |
+
|
| 339 |
+
<!-- Right Column: Opportunities -->
|
| 340 |
+
<div class="space-y-8">
|
| 341 |
+
<!-- Top Opportunities -->
|
| 342 |
+
<div class="chart-container p-6">
|
| 343 |
+
<h3 class="text-xl font-bold text-gray-800 mb-4">Top Opportunities</h3>
|
| 344 |
+
<div id="opportunities-list" class="space-y-3">
|
| 345 |
+
<div class="opportunity-card bg-gradient-to-r from-indigo-50 to-purple-50 p-4 rounded-lg">
|
| 346 |
+
<div class="flex justify-between items-start mb-2">
|
| 347 |
+
<h4 class="font-semibold text-gray-800 text-sm">AI Productivity Tools</h4>
|
| 348 |
+
<span class="text-xs bg-green-100 text-green-800 px-2 py-1 rounded-full">High</span>
|
| 349 |
+
</div>
|
| 350 |
+
<p class="text-xs text-gray-600 mb-2">Growing demand for AI-powered productivity solutions</p>
|
| 351 |
+
<div class="flex justify-between items-center">
|
| 352 |
+
<span class="text-xs text-gray-500">Evidence: 0.85</span>
|
| 353 |
+
<span class="text-xs text-indigo-600 font-semibold">Explore →</span>
|
| 354 |
+
</div>
|
| 355 |
+
</div>
|
| 356 |
+
<div class="opportunity-card bg-gradient-to-r from-cyan-50 to-blue-50 p-4 rounded-lg">
|
| 357 |
+
<div class="flex justify-between items-start mb-2">
|
| 358 |
+
<h4 class="font-semibold text-gray-800 text-sm">Remote Work Platforms</h4>
|
| 359 |
+
<span class="text-xs bg-yellow-100 text-yellow-800 px-2 py-1 rounded-full">Medium</span>
|
| 360 |
+
</div>
|
| 361 |
+
<p class="text-xs text-gray-600 mb-2">Hybrid work solutions gaining traction</p>
|
| 362 |
+
<div class="flex justify-between items-center">
|
| 363 |
+
<span class="text-xs text-gray-500">Evidence: 0.72</span>
|
| 364 |
+
<span class="text-xs text-cyan-600 font-semibold">Explore →</span>
|
| 365 |
+
</div>
|
| 366 |
+
</div>
|
| 367 |
+
<div class="opportunity-card bg-gradient-to-r from-emerald-50 to-teal-50 p-4 rounded-lg">
|
| 368 |
+
<div class="flex justify-between items-start mb-2">
|
| 369 |
+
<h4 class="font-semibold text-gray-800 text-sm">Sustainable Packaging</h4>
|
| 370 |
+
<span class="text-xs bg-green-100 text-green-800 px-2 py-1 rounded-full">High</span>
|
| 371 |
+
</div>
|
| 372 |
+
<p class="text-xs text-gray-600 mb-2">Eco-friendly packaging solutions in demand</p>
|
| 373 |
+
<div class="flex justify-between items-center">
|
| 374 |
+
<span class="text-xs text-gray-500">Evidence: 0.68</span>
|
| 375 |
+
<span class="text-xs text-emerald-600 font-semibold">Explore →</span>
|
| 376 |
+
</div>
|
| 377 |
+
</div>
|
| 378 |
+
</div>
|
| 379 |
+
</div>
|
| 380 |
+
|
| 381 |
+
<!-- Market Gaps -->
|
| 382 |
+
<div class="chart-container p-6">
|
| 383 |
+
<h3 class="text-xl font-bold text-gray-800 mb-4">Market Gaps</h3>
|
| 384 |
+
<div id="market-gaps-chart" style="height: 200px;"></div>
|
| 385 |
+
</div>
|
| 386 |
+
|
| 387 |
+
<!-- Keyword Network -->
|
| 388 |
+
<div class="chart-container p-6">
|
| 389 |
+
<h3 class="text-xl font-bold text-gray-800 mb-4">Keyword Network</h3>
|
| 390 |
+
<div id="keyword-network-chart" style="height: 250px;"></div>
|
| 391 |
+
</div>
|
| 392 |
+
</div>
|
| 393 |
+
</div>
|
| 394 |
+
|
| 395 |
+
<!-- Detailed Analysis Section -->
|
| 396 |
+
<div class="mt-12 animate-fade-in" data-aos="fade-up" data-aos-delay="500">
|
| 397 |
+
<div class="chart-container p-8">
|
| 398 |
+
<h3 class="text-2xl font-bold text-gray-800 mb-6">Detailed Market Analysis</h3>
|
| 399 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
| 400 |
+
<div>
|
| 401 |
+
<h4 class="text-lg font-semibold text-gray-700 mb-4">Cross-Platform Correlations</h4>
|
| 402 |
+
<div id="correlations-chart" style="height: 300px;"></div>
|
| 403 |
+
</div>
|
| 404 |
+
<div>
|
| 405 |
+
<h4 class="text-lg font-semibold text-gray-700 mb-4">ROI Estimates</h4>
|
| 406 |
+
<div id="roi-chart" style="height: 300px;"></div>
|
| 407 |
+
</div>
|
| 408 |
+
</div>
|
| 409 |
+
</div>
|
| 410 |
+
</div>
|
| 411 |
+
</div>
|
| 412 |
+
|
| 413 |
+
<!-- Loading Overlay -->
|
| 414 |
+
<div id="loading-overlay" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center hidden">
|
| 415 |
+
<div class="glass-morphism p-8 rounded-lg text-center">
|
| 416 |
+
<div class="loading-spinner mx-auto mb-4"></div>
|
| 417 |
+
<p class="text-white text-lg">Analyzing market trends...</p>
|
| 418 |
+
<p class="text-white/70 text-sm mt-2">This may take a few moments</p>
|
| 419 |
+
</div>
|
| 420 |
+
</div>
|
| 421 |
+
|
| 422 |
+
<!-- Scripts -->
|
| 423 |
+
<script src="https://cdn.jsdelivr.net/npm/three@0.155.0/build/three.min.js"></script>
|
| 424 |
+
<script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script>
|
| 425 |
+
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
| 426 |
+
|
| 427 |
+
<script>
|
| 428 |
+
// Initialize AOS
|
| 429 |
+
AOS.init({
|
| 430 |
+
duration: 800,
|
| 431 |
+
once: true
|
| 432 |
+
});
|
| 433 |
+
|
| 434 |
+
// Initialize Feather Icons
|
| 435 |
+
feather.replace();
|
| 436 |
+
|
| 437 |
+
// Initialize Vanta Background
|
| 438 |
+
VANTA.GLOBE({
|
| 439 |
+
el: "#vanta-bg",
|
| 440 |
+
mouseControls: true,
|
| 441 |
+
touchControls: true,
|
| 442 |
+
gyroControls: false,
|
| 443 |
+
minHeight: 200.00,
|
| 444 |
+
minWidth: 200.00,
|
| 445 |
+
scale: 1.00,
|
| 446 |
+
scaleMobile: 1.00,
|
| 447 |
+
color: 0x6366f1,
|
| 448 |
+
backgroundColor: 0x1e293b,
|
| 449 |
+
size: 1.2
|
| 450 |
+
});
|
| 451 |
+
|
| 452 |
+
// Sample data for charts
|
| 453 |
+
const sampleData = {
|
| 454 |
+
timeline: {
|
| 455 |
+
keywords: ['AI productivity', 'remote work', 'sustainable packaging'],
|
| 456 |
+
dates: Array.from({length: 30}, (_, i) => {
|
| 457 |
+
const date = new Date();
|
| 458 |
+
date.setDate(date.getDate() - (29 - i));
|
| 459 |
+
return date.toISOString().split('T')[0];
|
| 460 |
+
}),
|
| 461 |
+
volumes: {
|
| 462 |
+
'AI productivity': Array.from({length: 30}, () => Math.floor(Math.random() * 1000) + 500),
|
| 463 |
+
'remote work': Array.from({length: 30}, () => Math.floor(Math.random() * 800) + 300),
|
| 464 |
+
'sustainable packaging': Array.from({length: 30}, () => Math.floor(Math.random() * 600) + 200)
|
| 465 |
+
}
|
| 466 |
+
},
|
| 467 |
+
sources: {
|
| 468 |
+
'Reddit': 35,
|
| 469 |
+
'Twitter': 25,
|
| 470 |
+
'GitHub': 20,
|
| 471 |
+
'News': 15,
|
| 472 |
+
'Google Trends': 5
|
| 473 |
+
},
|
| 474 |
+
sentiments: {
|
| 475 |
+
'AI productivity': 0.72,
|
| 476 |
+
'remote work': 0.45,
|
| 477 |
+
'sustainable packaging': 0.68
|
| 478 |
+
},
|
| 479 |
+
opportunities: [
|
| 480 |
+
{ name: 'AI Productivity Tools', score: 0.85, confidence: 0.92 },
|
| 481 |
+
{ name: 'Remote Work Platforms', score: 0.72, confidence: 0.78 },
|
| 482 |
+
{ name: 'Sustainable Packaging', score: 0.68, confidence: 0.85 },
|
| 483 |
+
{ name: 'Mental Health Apps', score: 0.65, confidence: 0.73 },
|
| 484 |
+
{ name: 'Electric Vehicle Charging', score: 0.58, confidence: 0.69 }
|
| 485 |
+
],
|
| 486 |
+
marketGaps: [
|
| 487 |
+
{ name: 'Supply Gap 1', value: 45 },
|
| 488 |
+
{ name: 'Demand Indicator', value: 35 },
|
| 489 |
+
{ name: 'Competition Void', value: 20 }
|
| 490 |
+
]
|
| 491 |
+
};
|
| 492 |
+
|
| 493 |
+
// Initialize Charts
|
| 494 |
+
function initCharts() {
|
| 495 |
+
// Timeline Chart
|
| 496 |
+
const timelineChart = echarts.init(document.getElementById('trend-timeline-chart'));
|
| 497 |
+
const timelineOption = {
|
| 498 |
+
title: { text: 'Trend Volume Over Time', textStyle: { color: '#374151' } },
|
| 499 |
+
tooltip: { trigger: 'axis' },
|
| 500 |
+
legend: { data: sampleData.timeline.keywords, top: 30 },
|
| 501 |
+
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
| 502 |
+
xAxis: { type: 'category', data: sampleData.timeline.dates, axisLabel: { rotate: 45 } },
|
| 503 |
+
yAxis: { type: 'value', name: 'Volume' },
|
| 504 |
+
series: sampleData.timeline.keywords.map(keyword => ({
|
| 505 |
+
name: keyword,
|
| 506 |
+
type: 'line',
|
| 507 |
+
smooth: true,
|
| 508 |
+
data: sampleData.timeline.volumes[keyword],
|
| 509 |
+
lineStyle: { width: 3 },
|
| 510 |
+
areaStyle: { opacity: 0.1 }
|
| 511 |
+
}))
|
| 512 |
+
};
|
| 513 |
+
timelineChart.setOption(timelineOption);
|
| 514 |
+
|
| 515 |
+
// Source Distribution Chart
|
| 516 |
+
const sourceChart = echarts.init(document.getElementById('source-distribution-chart'));
|
| 517 |
+
const sourceOption = {
|
| 518 |
+
title: { text: 'Data Sources', textStyle: { color: '#374151' } },
|
| 519 |
+
tooltip: { trigger: 'item' },
|
| 520 |
+
series: [{
|
| 521 |
+
type: 'pie',
|
| 522 |
+
radius: ['40%', '70%'],
|
| 523 |
+
data: Object.entries(sampleData.sources).map(([name, value]) => ({ name, value })),
|
| 524 |
+
emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } }
|
| 525 |
+
}]
|
| 526 |
+
};
|
| 527 |
+
sourceChart.setOption(sourceOption);
|
| 528 |
+
|
| 529 |
+
// Sentiment Heatmap
|
| 530 |
+
const sentimentChart = echarts.init(document.getElementById('sentiment-heatmap-chart'));
|
| 531 |
+
const sentimentOption = {
|
| 532 |
+
title: { text: 'Sentiment by Keyword', textStyle: { color: '#374151' } },
|
| 533 |
+
tooltip: { position: 'top' },
|
| 534 |
+
grid: { height: '50%', top: '10%' },
|
| 535 |
+
xAxis: { type: 'category', data: ['Reddit', 'Twitter', 'GitHub', 'News'], splitArea: { show: true } },
|
| 536 |
+
yAxis: { type: 'category', data: sampleData.timeline.keywords, splitArea: { show: true } },
|
| 537 |
+
visualMap: { min: -1, max: 1, calculable: true, orient: 'horizontal', left: 'center', bottom: '15%' },
|
| 538 |
+
series: [{
|
| 539 |
+
name: 'Sentiment',
|
| 540 |
+
type: 'heatmap',
|
| 541 |
+
data: sampleData.timeline.keywords.flatMap((keyword, i) =>
|
| 542 |
+
['Reddit', 'Twitter', 'GitHub', 'News'].map((source, j) => [j, i, Math.random() * 2 - 1])
|
| 543 |
+
),
|
| 544 |
+
label: { show: true }
|
| 545 |
+
}]
|
| 546 |
+
};
|
| 547 |
+
sentimentChart.setOption(sentimentOption);
|
| 548 |
+
|
| 549 |
+
// Market Gaps Chart
|
| 550 |
+
const gapsChart = echarts.init(document.getElementById('market-gaps-chart'));
|
| 551 |
+
const gapsOption = {
|
| 552 |
+
title: { text: 'Gap Types', textStyle: { color: '#374151' } },
|
| 553 |
+
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
| 554 |
+
xAxis: { type: 'category', data: sampleData.marketGaps.map(item => item.name) },
|
| 555 |
+
yAxis: { type: 'value' },
|
| 556 |
+
series: [{
|
| 557 |
+
type: 'bar',
|
| 558 |
+
data: sampleData.marketGaps.map(item => item.value),
|
| 559 |
+
itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
| 560 |
+
{ offset: 0, color: '#6366f1' },
|
| 561 |
+
{ offset: 1, color: '#8b5cf6' }
|
| 562 |
+
]) }
|
| 563 |
+
}]
|
| 564 |
+
};
|
| 565 |
+
gapsChart.setOption(gapsOption);
|
| 566 |
+
|
| 567 |
+
// Keyword Network
|
| 568 |
+
const networkChart = echarts.init(document.getElementById('keyword-network-chart'));
|
| 569 |
+
const networkOption = {
|
| 570 |
+
title: { text: 'Keyword Relationships', textStyle: { color: '#374151' } },
|
| 571 |
+
series: [{
|
| 572 |
+
type: 'graph',
|
| 573 |
+
layout: 'force',
|
| 574 |
+
symbolSize: 50,
|
| 575 |
+
roam: true,
|
| 576 |
+
label: { show: true },
|
| 577 |
+
edgeSymbol: ['circle', 'arrow'],
|
| 578 |
+
edgeSymbolSize: [4, 10],
|
| 579 |
+
data: sampleData.timeline.keywords.map(name => ({ name, value: Math.random() * 100 })),
|
| 580 |
+
links: sampleData.timeline.keywords.flatMap((source, i) =>
|
| 581 |
+
sampleData.timeline.keywords.slice(i + 1).map(target => ({
|
| 582 |
+
source,
|
| 583 |
+
target,
|
| 584 |
+
value: Math.random() * 10
|
| 585 |
+
}))
|
| 586 |
+
),
|
| 587 |
+
lineStyle: { opacity: 0.9, width: 2, curveness: 0.3 }
|
| 588 |
+
}]
|
| 589 |
+
};
|
| 590 |
+
networkChart.setOption(networkOption);
|
| 591 |
+
|
| 592 |
+
// Correlations Chart
|
| 593 |
+
const corrChart = echarts.init(document.getElementById('correlations-chart'));
|
| 594 |
+
const corrOption = {
|
| 595 |
+
title: { text: 'Platform Correlations', textStyle: { color: '#374151' } },
|
| 596 |
+
xAxis: { type: 'category', data: ['Reddit', 'Twitter', 'GitHub', 'News'] },
|
| 597 |
+
yAxis: { type: 'category', data: ['Reddit', 'Twitter', 'GitHub', 'News'] },
|
| 598 |
+
visualMap: { min: 0, max: 1, calculable: true, orient: 'horizontal', left: 'center', bottom: '15%' },
|
| 599 |
+
series: [{
|
| 600 |
+
type: 'heatmap',
|
| 601 |
+
data: ['Reddit', 'Twitter', 'GitHub', 'News'].flatMap((source, i) =>
|
| 602 |
+
['Reddit', 'Twitter', 'GitHub', 'News'].map((target, j) => [j, i, i === j ? 1 : Math.random()])
|
| 603 |
+
)
|
| 604 |
+
}]
|
| 605 |
+
};
|
| 606 |
+
corrChart.setOption(corrOption);
|
| 607 |
+
|
| 608 |
+
// ROI Chart
|
| 609 |
+
const roiChart = echarts.init(document.getElementById('roi-chart'));
|
| 610 |
+
const roiOption = {
|
| 611 |
+
title: { text: 'ROI Potential', textStyle: { color: '#374151' } },
|
| 612 |
+
tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c}%' },
|
| 613 |
+
series: [{
|
| 614 |
+
name: 'ROI',
|
| 615 |
+
type: 'gauge',
|
| 616 |
+
detail: { formatter: '{value}%' },
|
| 617 |
+
data: [{ value: 75, name: 'Potential ROI' }]
|
| 618 |
+
}]
|
| 619 |
+
};
|
| 620 |
+
roiChart.setOption(roiOption);
|
| 621 |
+
|
| 622 |
+
// Responsive charts
|
| 623 |
+
window.addEventListener('resize', () => {
|
| 624 |
+
[timelineChart, sourceChart, sentimentChart, gapsChart, networkChart, corrChart, roiChart].forEach(chart => {
|
| 625 |
+
chart.resize();
|
| 626 |
+
});
|
| 627 |
+
});
|
| 628 |
+
}
|
| 629 |
+
|
| 630 |
+
// Update Metrics
|
| 631 |
+
function updateMetrics() {
|
| 632 |
+
document.getElementById('total-data-points').textContent = '12,847';
|
| 633 |
+
document.getElementById('market-gaps').textContent = '23';
|
| 634 |
+
document.getElementById('avg-sentiment').textContent = '0.68';
|
| 635 |
+
document.getElementById('confidence-score').textContent = '87%';
|
| 636 |
+
}
|
| 637 |
+
|
| 638 |
+
// Event Listeners
|
| 639 |
+
document.getElementById('analyze-btn').addEventListener('click', function() {
|
| 640 |
+
const loadingOverlay = document.getElementById('loading-overlay');
|
| 641 |
+
loadingOverlay.classList.remove('hidden');
|
| 642 |
+
|
| 643 |
+
setTimeout(() => {
|
| 644 |
+
loadingOverlay.classList.add('hidden');
|
| 645 |
+
initCharts();
|
| 646 |
+
updateMetrics();
|
| 647 |
+
}, 3000);
|
| 648 |
+
});
|
| 649 |
+
|
| 650 |
+
document.getElementById('refresh-btn').addEventListener('click', function() {
|
| 651 |
+
location.reload();
|
| 652 |
+
});
|
| 653 |
+
|
| 654 |
+
document.getElementById('export-btn').addEventListener('click', function() {
|
| 655 |
+
alert('Export functionality would be implemented here');
|
| 656 |
+
});
|
| 657 |
+
|
| 658 |
+
// Quick Scan
|
| 659 |
+
document.getElementById('quick-scan-btn').addEventListener('click', function() {
|
| 660 |
+
const loadingOverlay = document.getElementById('loading-overlay');
|
| 661 |
+
loadingOverlay.classList.remove('hidden');
|
| 662 |
+
|
| 663 |
+
setTimeout(() => {
|
| 664 |
+
loadingOverlay.classList.add('hidden');
|
| 665 |
+
// Show quick results
|
| 666 |
+
updateMetrics();
|
| 667 |
+
}, 1500);
|
| 668 |
+
});
|
| 669 |
+
|
| 670 |
+
// Initialize
|
| 671 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 672 |
+
initCharts();
|
| 673 |
+
updateMetrics();
|
| 674 |
+
});
|
| 675 |
+
</script>
|
| 676 |
+
</body>
|
| 677 |
</html>
|
|
The diff for this file is too large to render.
See raw diff
|
|
|