Spaces:
Sleeping
Sleeping
QAway-to
commited on
Commit
·
7897e32
1
Parent(s):
49d9149
New tabs and functions v3.6
Browse files- app.py +36 -16
- core/crypto_dashboard.py +17 -16
- core/styles/crypto_dashboard.css +30 -8
app.py
CHANGED
|
@@ -81,34 +81,54 @@ with gr.Blocks(css=base_css) as demo:
|
|
| 81 |
|
| 82 |
# --- Crypto Intelligence Dashboard (Plotly Edition + KPI line) ---
|
| 83 |
with gr.TabItem("Crypto Intelligence Dashboard"):
|
|
|
|
| 84 |
gr.HTML(f"<style>{crypto_css}</style>")
|
| 85 |
-
gr.Markdown("### 💹 Coinlore Market Dashboard (Plotly Edition)")
|
| 86 |
|
| 87 |
-
# KPI line (фиксированная
|
| 88 |
-
with gr.Row():
|
| 89 |
-
kpi_line = gr.HTML(elem_id="kpi_line")
|
| 90 |
-
|
| 91 |
-
# Controls
|
| 92 |
-
with gr.Row():
|
| 93 |
-
top_slider = gr.Slider(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
load_btn = gr.Button("Generate Dashboard", variant="primary", scale=30)
|
| 95 |
|
| 96 |
-
#
|
| 97 |
-
with gr.Row(equal_height=True):
|
| 98 |
with gr.Column(scale=70):
|
| 99 |
treemap_plot = gr.Plot(label="Market Composition")
|
| 100 |
with gr.Column(scale=30):
|
| 101 |
-
ai_box = gr.Textbox(
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
movers_plot = gr.Plot(label="Top Movers", scale=50)
|
| 106 |
scatter_plot = gr.Plot(label="Market Cap vs Volume", scale=50)
|
| 107 |
|
|
|
|
|
|
|
| 108 |
def run_dash(n):
|
| 109 |
fig_treemap, fig_bar, fig_bubble, ai_comment, kpi_text = build_crypto_dashboard(n)
|
| 110 |
-
#
|
| 111 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
load_btn.click(
|
| 114 |
fn=run_dash,
|
|
|
|
| 81 |
|
| 82 |
# --- Crypto Intelligence Dashboard (Plotly Edition + KPI line) ---
|
| 83 |
with gr.TabItem("Crypto Intelligence Dashboard"):
|
| 84 |
+
# Подключаем стили вкладки
|
| 85 |
gr.HTML(f"<style>{crypto_css}</style>")
|
|
|
|
| 86 |
|
| 87 |
+
# KPI line (фиксированная строка без скачков, с минимальными отступами)
|
| 88 |
+
with gr.Row(elem_id="kpi_row"):
|
| 89 |
+
kpi_line = gr.HTML(elem_id="kpi_line")
|
| 90 |
+
|
| 91 |
+
# Controls (Top N slider + кнопка)
|
| 92 |
+
with gr.Row(elem_id="controls_row"):
|
| 93 |
+
top_slider = gr.Slider(
|
| 94 |
+
label="Top N coins",
|
| 95 |
+
minimum=20,
|
| 96 |
+
maximum=100,
|
| 97 |
+
step=10,
|
| 98 |
+
value=50,
|
| 99 |
+
scale=70,
|
| 100 |
+
)
|
| 101 |
load_btn = gr.Button("Generate Dashboard", variant="primary", scale=30)
|
| 102 |
|
| 103 |
+
# Основной контент — графики и текстовый инсайт
|
| 104 |
+
with gr.Row(equal_height=True, elem_id="main_charts_row"):
|
| 105 |
with gr.Column(scale=70):
|
| 106 |
treemap_plot = gr.Plot(label="Market Composition")
|
| 107 |
with gr.Column(scale=30):
|
| 108 |
+
ai_box = gr.Textbox(
|
| 109 |
+
label="AI Market Summary",
|
| 110 |
+
lines=18,
|
| 111 |
+
elem_id="ai_summary_sidebar",
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
# Нижний ряд с двумя графиками
|
| 115 |
+
with gr.Row(equal_height=True, elem_id="bottom_charts_row"):
|
| 116 |
movers_plot = gr.Plot(label="Top Movers", scale=50)
|
| 117 |
scatter_plot = gr.Plot(label="Market Cap vs Volume", scale=50)
|
| 118 |
|
| 119 |
+
|
| 120 |
+
# === Callback ===
|
| 121 |
def run_dash(n):
|
| 122 |
fig_treemap, fig_bar, fig_bubble, ai_comment, kpi_text = build_crypto_dashboard(n)
|
| 123 |
+
# KPI-строка уже готова, возвращаем её с обёрткой <div>
|
| 124 |
+
return (
|
| 125 |
+
fig_treemap,
|
| 126 |
+
fig_bar,
|
| 127 |
+
fig_bubble,
|
| 128 |
+
ai_comment,
|
| 129 |
+
f"<div class='kpi-line'>{kpi_text}</div>",
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
|
| 133 |
load_btn.click(
|
| 134 |
fn=run_dash,
|
core/crypto_dashboard.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
"""
|
| 2 |
-
Crypto Dashboard — Plotly Edition
|
| 3 |
-
|
|
|
|
|
|
|
| 4 |
"""
|
| 5 |
import requests
|
| 6 |
import pandas as pd
|
|
@@ -19,7 +21,7 @@ def fetch_coinlore_data(limit=100):
|
|
| 19 |
|
| 20 |
|
| 21 |
def _kpi_line(df) -> str:
|
| 22 |
-
"""
|
| 23 |
tracked = ["BTC", "ETH", "SOL", "DOGE"]
|
| 24 |
parts = []
|
| 25 |
for sym in tracked:
|
|
@@ -31,8 +33,8 @@ def _kpi_line(df) -> str:
|
|
| 31 |
arrow = "↑" if ch > 0 else "↓"
|
| 32 |
color = "#4ade80" if ch > 0 else "#f87171"
|
| 33 |
parts.append(
|
| 34 |
-
f"<
|
| 35 |
-
f"<span style='color:{color}'>{arrow} {abs(ch):.2f}%</span
|
| 36 |
)
|
| 37 |
return " , ".join(parts)
|
| 38 |
|
|
@@ -40,7 +42,7 @@ def _kpi_line(df) -> str:
|
|
| 40 |
def build_crypto_dashboard(top_n=50):
|
| 41 |
df = fetch_coinlore_data(top_n)
|
| 42 |
|
| 43 |
-
# === Treemap
|
| 44 |
fig_treemap = px.treemap(
|
| 45 |
df,
|
| 46 |
path=["symbol"],
|
|
@@ -50,15 +52,15 @@ def build_crypto_dashboard(top_n=50):
|
|
| 50 |
height=420,
|
| 51 |
)
|
| 52 |
fig_treemap.update_layout(
|
| 53 |
-
title=None,
|
| 54 |
template="plotly_dark",
|
| 55 |
-
|
| 56 |
margin=dict(l=5, r=5, t=5, b=5),
|
| 57 |
paper_bgcolor="rgba(0,0,0,0)",
|
| 58 |
plot_bgcolor="rgba(0,0,0,0)",
|
| 59 |
)
|
| 60 |
|
| 61 |
-
# ===
|
| 62 |
top = df.sort_values("percent_change_24h", ascending=False).head(12)
|
| 63 |
fig_bar = px.bar(
|
| 64 |
top,
|
|
@@ -70,15 +72,15 @@ def build_crypto_dashboard(top_n=50):
|
|
| 70 |
height=320,
|
| 71 |
)
|
| 72 |
fig_bar.update_layout(
|
| 73 |
-
title=None,
|
| 74 |
template="plotly_dark",
|
| 75 |
-
|
| 76 |
margin=dict(l=40, r=10, t=5, b=18),
|
| 77 |
paper_bgcolor="rgba(0,0,0,0)",
|
| 78 |
plot_bgcolor="rgba(0,0,0,0)",
|
| 79 |
)
|
| 80 |
|
| 81 |
-
# === Market Cap vs Volume ===
|
| 82 |
fig_bubble = px.scatter(
|
| 83 |
df.head(60),
|
| 84 |
x="market_cap_usd",
|
|
@@ -92,18 +94,17 @@ def build_crypto_dashboard(top_n=50):
|
|
| 92 |
height=320,
|
| 93 |
)
|
| 94 |
fig_bubble.update_layout(
|
| 95 |
-
title=None,
|
| 96 |
template="plotly_dark",
|
| 97 |
-
|
| 98 |
margin=dict(l=36, r=10, t=5, b=18),
|
| 99 |
paper_bgcolor="rgba(0,0,0,0)",
|
| 100 |
plot_bgcolor="rgba(0,0,0,0)",
|
| 101 |
)
|
| 102 |
|
| 103 |
-
# ===
|
| 104 |
summary = _ai_summary(df)
|
| 105 |
kpi_text = _kpi_line(df)
|
| 106 |
-
|
| 107 |
return fig_treemap, fig_bar, fig_bubble, summary, kpi_text
|
| 108 |
|
| 109 |
|
|
|
|
| 1 |
"""
|
| 2 |
+
Crypto Dashboard — Plotly Edition (clean layout)
|
| 3 |
+
• убраны colorbar заголовки (percent_change_*)
|
| 4 |
+
• уменьшены отступы KPI
|
| 5 |
+
• без глобального Markdown-заголовка
|
| 6 |
"""
|
| 7 |
import requests
|
| 8 |
import pandas as pd
|
|
|
|
| 21 |
|
| 22 |
|
| 23 |
def _kpi_line(df) -> str:
|
| 24 |
+
"""Формирует компактную KPI-строку без лишних пробелов"""
|
| 25 |
tracked = ["BTC", "ETH", "SOL", "DOGE"]
|
| 26 |
parts = []
|
| 27 |
for sym in tracked:
|
|
|
|
| 33 |
arrow = "↑" if ch > 0 else "↓"
|
| 34 |
color = "#4ade80" if ch > 0 else "#f87171"
|
| 35 |
parts.append(
|
| 36 |
+
f"<b>{sym}</b> ${price:,.0f} "
|
| 37 |
+
f"<span style='color:{color}'>{arrow} {abs(ch):.2f}%</span>"
|
| 38 |
)
|
| 39 |
return " , ".join(parts)
|
| 40 |
|
|
|
|
| 42 |
def build_crypto_dashboard(top_n=50):
|
| 43 |
df = fetch_coinlore_data(top_n)
|
| 44 |
|
| 45 |
+
# === Treemap ===
|
| 46 |
fig_treemap = px.treemap(
|
| 47 |
df,
|
| 48 |
path=["symbol"],
|
|
|
|
| 52 |
height=420,
|
| 53 |
)
|
| 54 |
fig_treemap.update_layout(
|
| 55 |
+
title=None,
|
| 56 |
template="plotly_dark",
|
| 57 |
+
coloraxis_colorbar=dict(title=None), # 🔹 убираем надпись percent_change_24h
|
| 58 |
margin=dict(l=5, r=5, t=5, b=5),
|
| 59 |
paper_bgcolor="rgba(0,0,0,0)",
|
| 60 |
plot_bgcolor="rgba(0,0,0,0)",
|
| 61 |
)
|
| 62 |
|
| 63 |
+
# === Bar chart (Top gainers) ===
|
| 64 |
top = df.sort_values("percent_change_24h", ascending=False).head(12)
|
| 65 |
fig_bar = px.bar(
|
| 66 |
top,
|
|
|
|
| 72 |
height=320,
|
| 73 |
)
|
| 74 |
fig_bar.update_layout(
|
| 75 |
+
title=None,
|
| 76 |
template="plotly_dark",
|
| 77 |
+
coloraxis_colorbar=dict(title=None), # 🔹 убираем надпись percent_change_24h
|
| 78 |
margin=dict(l=40, r=10, t=5, b=18),
|
| 79 |
paper_bgcolor="rgba(0,0,0,0)",
|
| 80 |
plot_bgcolor="rgba(0,0,0,0)",
|
| 81 |
)
|
| 82 |
|
| 83 |
+
# === Scatter (Market Cap vs Volume) ===
|
| 84 |
fig_bubble = px.scatter(
|
| 85 |
df.head(60),
|
| 86 |
x="market_cap_usd",
|
|
|
|
| 94 |
height=320,
|
| 95 |
)
|
| 96 |
fig_bubble.update_layout(
|
| 97 |
+
title=None,
|
| 98 |
template="plotly_dark",
|
| 99 |
+
coloraxis_colorbar=dict(title=None), # 🔹 убираем надпись percent_change_7d
|
| 100 |
margin=dict(l=36, r=10, t=5, b=18),
|
| 101 |
paper_bgcolor="rgba(0,0,0,0)",
|
| 102 |
plot_bgcolor="rgba(0,0,0,0)",
|
| 103 |
)
|
| 104 |
|
| 105 |
+
# === LLM summary ===
|
| 106 |
summary = _ai_summary(df)
|
| 107 |
kpi_text = _kpi_line(df)
|
|
|
|
| 108 |
return fig_treemap, fig_bar, fig_bubble, summary, kpi_text
|
| 109 |
|
| 110 |
|
core/styles/crypto_dashboard.css
CHANGED
|
@@ -1,29 +1,51 @@
|
|
| 1 |
/* === KPI fixed line (строка с метриками) === */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
#kpi_line {
|
| 3 |
width: 100%;
|
| 4 |
-
|
|
|
|
| 5 |
background: transparent;
|
| 6 |
color: #f0f6fc;
|
| 7 |
font-family: 'JetBrains Mono', monospace;
|
|
|
|
|
|
|
| 8 |
white-space: nowrap;
|
| 9 |
overflow: hidden;
|
| 10 |
text-overflow: ellipsis;
|
| 11 |
display: flex;
|
| 12 |
align-items: center;
|
| 13 |
justify-content: flex-start;
|
| 14 |
-
min-height:
|
| 15 |
border-bottom: 1px solid #30363d;
|
|
|
|
| 16 |
}
|
|
|
|
|
|
|
| 17 |
.kpi-item {
|
| 18 |
-
margin-right:
|
| 19 |
-
|
| 20 |
-
letter-spacing: 0.1px;
|
| 21 |
transition: opacity 0.2s ease;
|
| 22 |
}
|
| 23 |
.kpi-item:hover {
|
| 24 |
opacity: 0.85;
|
| 25 |
}
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
/* === Plot containers — плотное выравнивание === */
|
| 28 |
[data-testid="plot-container"] {
|
| 29 |
width: 100% !important;
|
|
@@ -77,7 +99,7 @@
|
|
| 77 |
|
| 78 |
/* === Ряды между графиками === */
|
| 79 |
.gr-row {
|
| 80 |
-
gap:
|
| 81 |
-
margin-top:
|
| 82 |
-
margin-bottom:
|
| 83 |
}
|
|
|
|
| 1 |
/* === KPI fixed line (строка с метриками) === */
|
| 2 |
+
#kpi_row {
|
| 3 |
+
margin-top: 0 !important;
|
| 4 |
+
margin-bottom: 0 !important;
|
| 5 |
+
padding: 0 !important;
|
| 6 |
+
}
|
| 7 |
#kpi_line {
|
| 8 |
width: 100%;
|
| 9 |
+
margin: 0 !important;
|
| 10 |
+
padding: 2px 6px !important;
|
| 11 |
background: transparent;
|
| 12 |
color: #f0f6fc;
|
| 13 |
font-family: 'JetBrains Mono', monospace;
|
| 14 |
+
font-size: 14px;
|
| 15 |
+
line-height: 1.2;
|
| 16 |
white-space: nowrap;
|
| 17 |
overflow: hidden;
|
| 18 |
text-overflow: ellipsis;
|
| 19 |
display: flex;
|
| 20 |
align-items: center;
|
| 21 |
justify-content: flex-start;
|
| 22 |
+
min-height: 24px;
|
| 23 |
border-bottom: 1px solid #30363d;
|
| 24 |
+
transition: all 0.2s ease-in-out;
|
| 25 |
}
|
| 26 |
+
|
| 27 |
+
/* KPI items */
|
| 28 |
.kpi-item {
|
| 29 |
+
margin-right: 14px;
|
| 30 |
+
letter-spacing: 0.2px;
|
|
|
|
| 31 |
transition: opacity 0.2s ease;
|
| 32 |
}
|
| 33 |
.kpi-item:hover {
|
| 34 |
opacity: 0.85;
|
| 35 |
}
|
| 36 |
|
| 37 |
+
/* === Controls (slider + button) — минимальные отступы === */
|
| 38 |
+
#controls_row {
|
| 39 |
+
margin-top: 2px !important;
|
| 40 |
+
margin-bottom: 2px !important;
|
| 41 |
+
padding: 0 !important;
|
| 42 |
+
}
|
| 43 |
+
#controls_row .gr-button,
|
| 44 |
+
#controls_row .gr-slider {
|
| 45 |
+
margin-top: 0 !important;
|
| 46 |
+
margin-bottom: 0 !important;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
/* === Plot containers — плотное выравнивание === */
|
| 50 |
[data-testid="plot-container"] {
|
| 51 |
width: 100% !important;
|
|
|
|
| 99 |
|
| 100 |
/* === Ряды между графиками === */
|
| 101 |
.gr-row {
|
| 102 |
+
gap: 10px !important;
|
| 103 |
+
margin-top: 2px !important;
|
| 104 |
+
margin-bottom: 2px !important;
|
| 105 |
}
|