Spaces:
Running
Running
QAway-to
commited on
Commit
·
f2b5dca
1
Parent(s):
c1d305f
New tabs and functions v3.0
Browse files- app.py +38 -13
- core/crypto_dashboard.py +42 -20
- core/multi_charts.py +67 -0
- core/ui_style.css +51 -19
- requirements.txt +2 -0
app.py
CHANGED
|
@@ -63,25 +63,40 @@ with gr.Blocks(css=custom_css) as demo:
|
|
| 63 |
metrics_out = gr.Dataframe(label="Portfolio Metrics", wrap=True)
|
| 64 |
metrics_btn.click(fn=show_metrics_table, inputs=metrics_in, outputs=metrics_out)
|
| 65 |
|
| 66 |
-
# ---
|
| 67 |
-
with gr.TabItem("
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
with gr.TabItem("Crypto Intelligence Dashboard"):
|
| 75 |
gr.Markdown("### 💹 Coinlore Market Dashboard (Plotly Edition)")
|
| 76 |
|
| 77 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
with gr.Row():
|
| 79 |
top_slider = gr.Slider(
|
| 80 |
label="Top N coins", minimum=20, maximum=100, step=10, value=50, scale=70
|
| 81 |
)
|
| 82 |
load_btn = gr.Button("Generate Dashboard", variant="primary", scale=30)
|
| 83 |
|
| 84 |
-
#
|
| 85 |
with gr.Row(equal_height=True):
|
| 86 |
with gr.Column(scale=70):
|
| 87 |
treemap_plot = gr.Plot(label="Market Composition")
|
|
@@ -90,7 +105,7 @@ with gr.Blocks(css=custom_css) as demo:
|
|
| 90 |
label="AI Market Summary", lines=18, elem_id="ai_summary_sidebar"
|
| 91 |
)
|
| 92 |
|
| 93 |
-
#
|
| 94 |
with gr.Row(equal_height=True):
|
| 95 |
movers_plot = gr.Plot(label="Top Movers", scale=50)
|
| 96 |
scatter_plot = gr.Plot(label="Market Cap vs Volume", scale=50)
|
|
@@ -99,13 +114,23 @@ with gr.Blocks(css=custom_css) as demo:
|
|
| 99 |
|
| 100 |
|
| 101 |
def run_dash(n):
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
|
| 104 |
|
| 105 |
load_btn.click(
|
| 106 |
fn=run_dash,
|
| 107 |
inputs=top_slider,
|
| 108 |
-
outputs=[treemap_plot, movers_plot, scatter_plot, ai_box],
|
| 109 |
show_progress="minimal",
|
| 110 |
)
|
| 111 |
|
|
|
|
| 63 |
metrics_out = gr.Dataframe(label="Portfolio Metrics", wrap=True)
|
| 64 |
metrics_btn.click(fn=show_metrics_table, inputs=metrics_in, outputs=metrics_out)
|
| 65 |
|
| 66 |
+
# --- Multi Visualization (ECharts + Highcharts) ---
|
| 67 |
+
with gr.TabItem("Visual Comparison (ECharts / Highcharts)"):
|
| 68 |
+
gr.Markdown("### 🌐 Multi Visualization Demo — ECharts & Highcharts")
|
| 69 |
+
|
| 70 |
+
from core.multi_charts import build_echarts_line, build_highcharts_demo
|
| 71 |
+
|
| 72 |
+
with gr.Row(equal_height=True):
|
| 73 |
+
echarts_html = gr.HTML(label="ECharts BTC/USD")
|
| 74 |
+
highcharts_html = gr.HTML(label="Highcharts ETH/USD")
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def render_all():
|
| 78 |
+
return build_echarts_line(), build_highcharts_demo()
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
render_btn = gr.Button("Generate Charts", variant="primary")
|
| 82 |
+
render_btn.click(fn=render_all, outputs=[echarts_html, highcharts_html])
|
| 83 |
+
|
| 84 |
+
# --- Crypto Intelligence Dashboard (Plotly Edition + KPI) ---
|
| 85 |
with gr.TabItem("Crypto Intelligence Dashboard"):
|
| 86 |
gr.Markdown("### 💹 Coinlore Market Dashboard (Plotly Edition)")
|
| 87 |
|
| 88 |
+
# KPI row
|
| 89 |
+
with gr.Row():
|
| 90 |
+
kpi_html = gr.HTML(label="", elem_id="kpi_panel")
|
| 91 |
+
|
| 92 |
+
# Controls
|
| 93 |
with gr.Row():
|
| 94 |
top_slider = gr.Slider(
|
| 95 |
label="Top N coins", minimum=20, maximum=100, step=10, value=50, scale=70
|
| 96 |
)
|
| 97 |
load_btn = gr.Button("Generate Dashboard", variant="primary", scale=30)
|
| 98 |
|
| 99 |
+
# Treemap + AI Summary
|
| 100 |
with gr.Row(equal_height=True):
|
| 101 |
with gr.Column(scale=70):
|
| 102 |
treemap_plot = gr.Plot(label="Market Composition")
|
|
|
|
| 105 |
label="AI Market Summary", lines=18, elem_id="ai_summary_sidebar"
|
| 106 |
)
|
| 107 |
|
| 108 |
+
# Lower charts
|
| 109 |
with gr.Row(equal_height=True):
|
| 110 |
movers_plot = gr.Plot(label="Top Movers", scale=50)
|
| 111 |
scatter_plot = gr.Plot(label="Market Cap vs Volume", scale=50)
|
|
|
|
| 114 |
|
| 115 |
|
| 116 |
def run_dash(n):
|
| 117 |
+
figs = build_crypto_dashboard(n)
|
| 118 |
+
fig_treemap, fig_bar, fig_bubble, ai_comment, kpis = figs
|
| 119 |
+
kpi_html_str = "".join(
|
| 120 |
+
f"<div class='kpi-card' style='border-left:4px solid {c['color']}'>"
|
| 121 |
+
f"<span class='kpi-symbol'>{c['symbol']}</span>"
|
| 122 |
+
f"<span class='kpi-price'>{c['price']}</span>"
|
| 123 |
+
f"<span class='kpi-change' style='color:{c['color']}'>{c['change']}</span>"
|
| 124 |
+
"</div>"
|
| 125 |
+
for c in kpis
|
| 126 |
+
)
|
| 127 |
+
return fig_treemap, fig_bar, fig_bubble, ai_comment, kpi_html_str
|
| 128 |
|
| 129 |
|
| 130 |
load_btn.click(
|
| 131 |
fn=run_dash,
|
| 132 |
inputs=top_slider,
|
| 133 |
+
outputs=[treemap_plot, movers_plot, scatter_plot, ai_box, kpi_html],
|
| 134 |
show_progress="minimal",
|
| 135 |
)
|
| 136 |
|
core/crypto_dashboard.py
CHANGED
|
@@ -1,25 +1,48 @@
|
|
| 1 |
"""
|
| 2 |
-
Crypto Dashboard — Plotly Edition
|
| 3 |
-
|
|
|
|
| 4 |
"""
|
| 5 |
import requests
|
| 6 |
import pandas as pd
|
| 7 |
import plotly.express as px
|
| 8 |
-
import plotly.graph_objects as go
|
| 9 |
from services.llm_client import llm_service
|
| 10 |
|
| 11 |
|
| 12 |
-
# ===
|
| 13 |
def fetch_coinlore_data(limit=100):
|
| 14 |
url = "https://api.coinlore.net/api/tickers/"
|
| 15 |
data = requests.get(url).json()["data"]
|
| 16 |
df = pd.DataFrame(data)
|
| 17 |
-
for col in [
|
| 18 |
-
|
|
|
|
|
|
|
| 19 |
df[col] = pd.to_numeric(df[col], errors="coerce")
|
| 20 |
return df.head(limit)
|
| 21 |
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
# === Main Dashboard ===
|
| 24 |
def build_crypto_dashboard(top_n=50):
|
| 25 |
df = fetch_coinlore_data(top_n)
|
|
@@ -32,16 +55,16 @@ def build_crypto_dashboard(top_n=50):
|
|
| 32 |
color="percent_change_24h",
|
| 33 |
color_continuous_scale="RdYlGn",
|
| 34 |
title="Market Composition by Market Cap (Top Coins)",
|
| 35 |
-
height=
|
| 36 |
)
|
| 37 |
fig_treemap.update_layout(
|
| 38 |
template="plotly_dark",
|
| 39 |
-
margin=dict(l=
|
| 40 |
paper_bgcolor="rgba(0,0,0,0)",
|
| 41 |
plot_bgcolor="rgba(0,0,0,0)",
|
| 42 |
)
|
| 43 |
|
| 44 |
-
# --- Top Gainers
|
| 45 |
top = df.sort_values("percent_change_24h", ascending=False).head(12)
|
| 46 |
fig_bar = px.bar(
|
| 47 |
top,
|
|
@@ -51,16 +74,16 @@ def build_crypto_dashboard(top_n=50):
|
|
| 51 |
color="percent_change_24h",
|
| 52 |
color_continuous_scale="Blues",
|
| 53 |
title="Top 12 Gainers (24h)",
|
| 54 |
-
height=
|
| 55 |
)
|
| 56 |
fig_bar.update_layout(
|
| 57 |
template="plotly_dark",
|
| 58 |
-
margin=dict(l=
|
| 59 |
paper_bgcolor="rgba(0,0,0,0)",
|
| 60 |
plot_bgcolor="rgba(0,0,0,0)",
|
| 61 |
)
|
| 62 |
|
| 63 |
-
# --- Market Cap vs Volume
|
| 64 |
fig_bubble = px.scatter(
|
| 65 |
df.head(60),
|
| 66 |
x="market_cap_usd",
|
|
@@ -72,32 +95,31 @@ def build_crypto_dashboard(top_n=50):
|
|
| 72 |
log_y=True,
|
| 73 |
color_continuous_scale="RdYlGn",
|
| 74 |
title="Market Cap vs 24h Volume",
|
| 75 |
-
height=
|
| 76 |
)
|
| 77 |
fig_bubble.update_layout(
|
| 78 |
template="plotly_dark",
|
| 79 |
-
margin=dict(l=
|
| 80 |
paper_bgcolor="rgba(0,0,0,0)",
|
| 81 |
plot_bgcolor="rgba(0,0,0,0)",
|
| 82 |
)
|
| 83 |
|
| 84 |
-
# --- AI
|
| 85 |
summary = _generate_ai_summary(df)
|
| 86 |
-
|
| 87 |
-
return fig_treemap, fig_bar, fig_bubble, summary
|
| 88 |
|
| 89 |
|
| 90 |
def _generate_ai_summary(df):
|
| 91 |
leaders = df.sort_values("percent_change_24h", ascending=False).head(3)["symbol"].tolist()
|
| 92 |
laggards = df.sort_values("percent_change_24h").head(3)["symbol"].tolist()
|
| 93 |
-
|
| 94 |
prompt = f"""
|
| 95 |
Summarize today's crypto market based on Coinlore data.
|
| 96 |
Top gainers: {', '.join(leaders)}.
|
| 97 |
Top losers: {', '.join(laggards)}.
|
| 98 |
Include:
|
| 99 |
-
- overall
|
| 100 |
-
- volatility and liquidity
|
| 101 |
- short-term outlook
|
| 102 |
"""
|
| 103 |
summary = ""
|
|
|
|
| 1 |
"""
|
| 2 |
+
Crypto Dashboard — Plotly Edition + KPI Cards
|
| 3 |
+
Source: Coinlore API
|
| 4 |
+
Visual: Dense Power BI–style dashboard with live metrics.
|
| 5 |
"""
|
| 6 |
import requests
|
| 7 |
import pandas as pd
|
| 8 |
import plotly.express as px
|
|
|
|
| 9 |
from services.llm_client import llm_service
|
| 10 |
|
| 11 |
|
| 12 |
+
# === Load data ===
|
| 13 |
def fetch_coinlore_data(limit=100):
|
| 14 |
url = "https://api.coinlore.net/api/tickers/"
|
| 15 |
data = requests.get(url).json()["data"]
|
| 16 |
df = pd.DataFrame(data)
|
| 17 |
+
for col in [
|
| 18 |
+
"price_usd", "market_cap_usd", "volume24",
|
| 19 |
+
"percent_change_1h", "percent_change_24h", "percent_change_7d"
|
| 20 |
+
]:
|
| 21 |
df[col] = pd.to_numeric(df[col], errors="coerce")
|
| 22 |
return df.head(limit)
|
| 23 |
|
| 24 |
|
| 25 |
+
# === KPI builder ===
|
| 26 |
+
def build_kpi_cards(df):
|
| 27 |
+
tracked = ["BTC", "ETH", "SOL", "DOGE"]
|
| 28 |
+
cards = []
|
| 29 |
+
for sym in tracked:
|
| 30 |
+
coin = df[df["symbol"] == sym]
|
| 31 |
+
if coin.empty:
|
| 32 |
+
continue
|
| 33 |
+
price = float(coin["price_usd"])
|
| 34 |
+
change = float(coin["percent_change_24h"])
|
| 35 |
+
arrow = "▲" if change > 0 else "▼"
|
| 36 |
+
color = "#4ade80" if change > 0 else "#f87171"
|
| 37 |
+
cards.append({
|
| 38 |
+
"symbol": sym,
|
| 39 |
+
"price": f"${price:,.0f}",
|
| 40 |
+
"change": f"{arrow} {abs(change):.2f}%",
|
| 41 |
+
"color": color,
|
| 42 |
+
})
|
| 43 |
+
return cards
|
| 44 |
+
|
| 45 |
+
|
| 46 |
# === Main Dashboard ===
|
| 47 |
def build_crypto_dashboard(top_n=50):
|
| 48 |
df = fetch_coinlore_data(top_n)
|
|
|
|
| 55 |
color="percent_change_24h",
|
| 56 |
color_continuous_scale="RdYlGn",
|
| 57 |
title="Market Composition by Market Cap (Top Coins)",
|
| 58 |
+
height=360,
|
| 59 |
)
|
| 60 |
fig_treemap.update_layout(
|
| 61 |
template="plotly_dark",
|
| 62 |
+
margin=dict(l=10, r=10, t=25, b=10),
|
| 63 |
paper_bgcolor="rgba(0,0,0,0)",
|
| 64 |
plot_bgcolor="rgba(0,0,0,0)",
|
| 65 |
)
|
| 66 |
|
| 67 |
+
# --- Top Gainers ---
|
| 68 |
top = df.sort_values("percent_change_24h", ascending=False).head(12)
|
| 69 |
fig_bar = px.bar(
|
| 70 |
top,
|
|
|
|
| 74 |
color="percent_change_24h",
|
| 75 |
color_continuous_scale="Blues",
|
| 76 |
title="Top 12 Gainers (24h)",
|
| 77 |
+
height=320,
|
| 78 |
)
|
| 79 |
fig_bar.update_layout(
|
| 80 |
template="plotly_dark",
|
| 81 |
+
margin=dict(l=60, r=20, t=25, b=25),
|
| 82 |
paper_bgcolor="rgba(0,0,0,0)",
|
| 83 |
plot_bgcolor="rgba(0,0,0,0)",
|
| 84 |
)
|
| 85 |
|
| 86 |
+
# --- Market Cap vs Volume ---
|
| 87 |
fig_bubble = px.scatter(
|
| 88 |
df.head(60),
|
| 89 |
x="market_cap_usd",
|
|
|
|
| 95 |
log_y=True,
|
| 96 |
color_continuous_scale="RdYlGn",
|
| 97 |
title="Market Cap vs 24h Volume",
|
| 98 |
+
height=320,
|
| 99 |
)
|
| 100 |
fig_bubble.update_layout(
|
| 101 |
template="plotly_dark",
|
| 102 |
+
margin=dict(l=50, r=20, t=25, b=25),
|
| 103 |
paper_bgcolor="rgba(0,0,0,0)",
|
| 104 |
plot_bgcolor="rgba(0,0,0,0)",
|
| 105 |
)
|
| 106 |
|
| 107 |
+
# --- AI summary ---
|
| 108 |
summary = _generate_ai_summary(df)
|
| 109 |
+
kpis = build_kpi_cards(df)
|
| 110 |
+
return fig_treemap, fig_bar, fig_bubble, summary, kpis
|
| 111 |
|
| 112 |
|
| 113 |
def _generate_ai_summary(df):
|
| 114 |
leaders = df.sort_values("percent_change_24h", ascending=False).head(3)["symbol"].tolist()
|
| 115 |
laggards = df.sort_values("percent_change_24h").head(3)["symbol"].tolist()
|
|
|
|
| 116 |
prompt = f"""
|
| 117 |
Summarize today's crypto market based on Coinlore data.
|
| 118 |
Top gainers: {', '.join(leaders)}.
|
| 119 |
Top losers: {', '.join(laggards)}.
|
| 120 |
Include:
|
| 121 |
+
- overall sentiment
|
| 122 |
+
- volatility and liquidity
|
| 123 |
- short-term outlook
|
| 124 |
"""
|
| 125 |
summary = ""
|
core/multi_charts.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Multi Visualization Demo
|
| 3 |
+
Includes:
|
| 4 |
+
- ECharts (pyecharts)
|
| 5 |
+
- Highcharts (HTML embed)
|
| 6 |
+
"""
|
| 7 |
+
from pyecharts.charts import Line
|
| 8 |
+
from pyecharts import options as opts
|
| 9 |
+
import pandas as pd, numpy as np, datetime, json
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def build_echarts_line():
|
| 13 |
+
"""ECharts Line (BTC simulated data)"""
|
| 14 |
+
now = datetime.datetime.now()
|
| 15 |
+
dates = [now - datetime.timedelta(days=i) for i in range(90)][::-1]
|
| 16 |
+
prices = np.cumsum(np.random.randn(90)) + 42000
|
| 17 |
+
|
| 18 |
+
line = (
|
| 19 |
+
Line(init_opts=opts.InitOpts(bg_color="#0d1117", height="360px"))
|
| 20 |
+
.add_xaxis([d.strftime("%d %b") for d in dates])
|
| 21 |
+
.add_yaxis(
|
| 22 |
+
"BTC/USD",
|
| 23 |
+
prices.tolist(),
|
| 24 |
+
is_smooth=True,
|
| 25 |
+
linestyle_opts=opts.LineStyleOpts(width=2, color="#4f46e5"),
|
| 26 |
+
label_opts=opts.LabelOpts(is_show=False),
|
| 27 |
+
areastyle_opts=opts.AreaStyleOpts(opacity=0.15, color="#6366f1"),
|
| 28 |
+
)
|
| 29 |
+
.set_global_opts(
|
| 30 |
+
title_opts=opts.TitleOpts(
|
| 31 |
+
title="ECharts: BTC/USD (Simulated)",
|
| 32 |
+
title_textstyle_opts=opts.TextStyleOpts(color="#f0f6fc")
|
| 33 |
+
),
|
| 34 |
+
xaxis_opts=opts.AxisOpts(axisline_opts=opts.AxisLineOpts(line_style=opts.LineStyleOpts(color="#30363d"))),
|
| 35 |
+
yaxis_opts=opts.AxisOpts(axisline_opts=opts.AxisLineOpts(line_style=opts.LineStyleOpts(color="#30363d"))),
|
| 36 |
+
tooltip_opts=opts.TooltipOpts(trigger="axis"),
|
| 37 |
+
)
|
| 38 |
+
)
|
| 39 |
+
return line.render_embed()
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def build_highcharts_demo():
|
| 43 |
+
"""Highcharts HTML embed (ETH simulated data)"""
|
| 44 |
+
days = list(range(1, 91))
|
| 45 |
+
prices = (np.cumsum(np.random.randn(90)) * 15 + 2300).tolist()
|
| 46 |
+
|
| 47 |
+
js = f"""
|
| 48 |
+
<script src="https://code.highcharts.com/highcharts.js"></script>
|
| 49 |
+
<div id="highchart_eth" style="height:360px;"></div>
|
| 50 |
+
<script>
|
| 51 |
+
Highcharts.chart('highchart_eth', {{
|
| 52 |
+
chart: {{ backgroundColor: '#0d1117', style: {{ fontFamily: 'Inter' }} }},
|
| 53 |
+
title: {{ text: 'Highcharts: ETH/USD (Simulated)', style: {{ color: '#f0f6fc' }} }},
|
| 54 |
+
xAxis: {{ categories: {json.dumps(days)}, labels: {{ style: {{ color: '#9da5b4' }} }} }},
|
| 55 |
+
yAxis: {{ title: {{ text: 'Price (USD)', style: {{ color: '#9da5b4' }} }} }},
|
| 56 |
+
series: [{{
|
| 57 |
+
name: 'ETH/USD',
|
| 58 |
+
data: {json.dumps(prices)},
|
| 59 |
+
color: '#10b981',
|
| 60 |
+
}}],
|
| 61 |
+
legend: {{ itemStyle: {{ color: '#f0f6fc' }} }},
|
| 62 |
+
credits: {{ enabled: false }},
|
| 63 |
+
tooltip: {{ backgroundColor: '#161b22', borderColor: '#30363d', style: {{ color: '#f0f6fc' }} }}
|
| 64 |
+
}});
|
| 65 |
+
</script>
|
| 66 |
+
"""
|
| 67 |
+
return js
|
core/ui_style.css
CHANGED
|
@@ -14,6 +14,46 @@ h2, h3, .gr-markdown {
|
|
| 14 |
font-weight: 600 !important;
|
| 15 |
}
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
/* === Remove Gray Placeholder Icons === */
|
| 18 |
[data-testid="plot-container"] svg {
|
| 19 |
display: none !important;
|
|
@@ -23,28 +63,24 @@ h2, h3, .gr-markdown {
|
|
| 23 |
box-shadow: none !important;
|
| 24 |
}
|
| 25 |
|
| 26 |
-
/* === Plot
|
| 27 |
[data-testid="plot-container"] {
|
| 28 |
width: 100% !important;
|
| 29 |
-
margin: 0 auto
|
| 30 |
}
|
| 31 |
[data-testid="plot-container"] canvas {
|
| 32 |
width: 100% !important;
|
| 33 |
height: auto !important;
|
| 34 |
}
|
| 35 |
|
| 36 |
-
/*
|
| 37 |
-
#root [label="Market Composition"] canvas {
|
| 38 |
-
height: 400px !important;
|
| 39 |
-
}
|
| 40 |
#root [label="Top Movers"] canvas,
|
| 41 |
-
#root [label="Market Cap vs Volume"] canvas {
|
| 42 |
-
height: 350px !important;
|
| 43 |
-
}
|
| 44 |
|
| 45 |
-
/* === Sidebar
|
| 46 |
#ai_summary_sidebar textarea {
|
| 47 |
-
height:
|
| 48 |
background-color: #161b22 !important;
|
| 49 |
color: #f0f6fc !important;
|
| 50 |
border: 1px solid #30363d !important;
|
|
@@ -56,7 +92,7 @@ h2, h3, .gr-markdown {
|
|
| 56 |
resize: none !important;
|
| 57 |
}
|
| 58 |
|
| 59 |
-
/* ===
|
| 60 |
.gr-button {
|
| 61 |
border-radius: 6px !important;
|
| 62 |
font-weight: 600 !important;
|
|
@@ -64,7 +100,7 @@ h2, h3, .gr-markdown {
|
|
| 64 |
height: 52px !important;
|
| 65 |
background: linear-gradient(90deg, #4f46e5, #6366f1) !important;
|
| 66 |
border: none !important;
|
| 67 |
-
box-shadow: 0 2px 4px rgba(0,
|
| 68 |
transition: all 0.2s ease-in-out;
|
| 69 |
}
|
| 70 |
.gr-button:hover {
|
|
@@ -78,7 +114,7 @@ h2, h3, .gr-markdown {
|
|
| 78 |
background: #6366f1 !important;
|
| 79 |
}
|
| 80 |
|
| 81 |
-
/* ===
|
| 82 |
.gr-dataframe table {
|
| 83 |
width: 100% !important;
|
| 84 |
color: #c9d1d9 !important;
|
|
@@ -95,8 +131,4 @@ h2, h3, .gr-markdown {
|
|
| 95 |
border-top: 1px solid #30363d !important;
|
| 96 |
padding: 8px !important;
|
| 97 |
}
|
| 98 |
-
|
| 99 |
-
/* === Dashboard Layout === */
|
| 100 |
-
.gr-row {
|
| 101 |
-
gap: 18px !important;
|
| 102 |
-
}
|
|
|
|
| 14 |
font-weight: 600 !important;
|
| 15 |
}
|
| 16 |
|
| 17 |
+
/* === KPI Panel === */
|
| 18 |
+
#kpi_panel {
|
| 19 |
+
display: flex;
|
| 20 |
+
justify-content: space-between;
|
| 21 |
+
align-items: center;
|
| 22 |
+
margin: 12px 0 4px 0;
|
| 23 |
+
}
|
| 24 |
+
.kpi-card {
|
| 25 |
+
background-color: #161b22;
|
| 26 |
+
border-radius: 6px;
|
| 27 |
+
padding: 10px 14px;
|
| 28 |
+
width: 24%;
|
| 29 |
+
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
| 30 |
+
display: flex;
|
| 31 |
+
flex-direction: column;
|
| 32 |
+
}
|
| 33 |
+
.kpi-symbol {
|
| 34 |
+
font-weight: 700;
|
| 35 |
+
font-size: 15px;
|
| 36 |
+
color: #f0f6fc;
|
| 37 |
+
}
|
| 38 |
+
.kpi-price {
|
| 39 |
+
font-size: 20px;
|
| 40 |
+
font-weight: 600;
|
| 41 |
+
color: #f9fafb;
|
| 42 |
+
}
|
| 43 |
+
.kpi-change {
|
| 44 |
+
font-size: 13px;
|
| 45 |
+
margin-top: 2px;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
/* === Chart Embed (ECharts + Highcharts) === */
|
| 49 |
+
[data-testid="html"] {
|
| 50 |
+
width: 100% !important;
|
| 51 |
+
background: #0d1117 !important;
|
| 52 |
+
border: 1px solid #30363d !important;
|
| 53 |
+
border-radius: 6px !important;
|
| 54 |
+
overflow: hidden !important;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
/* === Remove Gray Placeholder Icons === */
|
| 58 |
[data-testid="plot-container"] svg {
|
| 59 |
display: none !important;
|
|
|
|
| 63 |
box-shadow: none !important;
|
| 64 |
}
|
| 65 |
|
| 66 |
+
/* === Plot Layout === */
|
| 67 |
[data-testid="plot-container"] {
|
| 68 |
width: 100% !important;
|
| 69 |
+
margin: 0 auto 16px auto !important;
|
| 70 |
}
|
| 71 |
[data-testid="plot-container"] canvas {
|
| 72 |
width: 100% !important;
|
| 73 |
height: auto !important;
|
| 74 |
}
|
| 75 |
|
| 76 |
+
/* === Chart Heights === */
|
| 77 |
+
#root [label="Market Composition"] canvas { height: 360px !important; }
|
|
|
|
|
|
|
| 78 |
#root [label="Top Movers"] canvas,
|
| 79 |
+
#root [label="Market Cap vs Volume"] canvas { height: 320px !important; }
|
|
|
|
|
|
|
| 80 |
|
| 81 |
+
/* === Sidebar === */
|
| 82 |
#ai_summary_sidebar textarea {
|
| 83 |
+
height: 360px !important;
|
| 84 |
background-color: #161b22 !important;
|
| 85 |
color: #f0f6fc !important;
|
| 86 |
border: 1px solid #30363d !important;
|
|
|
|
| 92 |
resize: none !important;
|
| 93 |
}
|
| 94 |
|
| 95 |
+
/* === Controls === */
|
| 96 |
.gr-button {
|
| 97 |
border-radius: 6px !important;
|
| 98 |
font-weight: 600 !important;
|
|
|
|
| 100 |
height: 52px !important;
|
| 101 |
background: linear-gradient(90deg, #4f46e5, #6366f1) !important;
|
| 102 |
border: none !important;
|
| 103 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.25);
|
| 104 |
transition: all 0.2s ease-in-out;
|
| 105 |
}
|
| 106 |
.gr-button:hover {
|
|
|
|
| 114 |
background: #6366f1 !important;
|
| 115 |
}
|
| 116 |
|
| 117 |
+
/* === Table / Row spacing === */
|
| 118 |
.gr-dataframe table {
|
| 119 |
width: 100% !important;
|
| 120 |
color: #c9d1d9 !important;
|
|
|
|
| 131 |
border-top: 1px solid #30363d !important;
|
| 132 |
padding: 8px !important;
|
| 133 |
}
|
| 134 |
+
.gr-row { gap: 16px !important; }
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
CHANGED
|
@@ -8,3 +8,5 @@ plotly
|
|
| 8 |
yfinance>=0.2.43
|
| 9 |
plotly>=6.3.1
|
| 10 |
altair
|
|
|
|
|
|
|
|
|
| 8 |
yfinance>=0.2.43
|
| 9 |
plotly>=6.3.1
|
| 10 |
altair
|
| 11 |
+
pyecharts
|
| 12 |
+
jinja2
|