262 lines
7.7 KiB
Python
262 lines
7.7 KiB
Python
"""
|
|
Custom Dash components for the interactive visualizer.
|
|
|
|
This module provides reusable UI components including the side panel,
|
|
navigation controls, and chart containers.
|
|
"""
|
|
|
|
from dash import html, dcc
|
|
import dash_bootstrap_components as dbc
|
|
import plotly.graph_objects as go
|
|
from plotly.subplots import make_subplots
|
|
|
|
|
|
def create_side_panel():
|
|
"""
|
|
Create the side panel component for displaying hover information and controls.
|
|
|
|
Returns:
|
|
dash component: Side panel layout
|
|
"""
|
|
return dbc.Card([
|
|
dbc.CardHeader("Chart Information"),
|
|
dbc.CardBody([
|
|
html.Div(id="hover-info", children=[
|
|
html.P("Hover over charts to see detailed information")
|
|
]),
|
|
html.Hr(),
|
|
html.Div([
|
|
dbc.Button("Reset CVD", id="reset-cvd-btn", color="primary", className="me-2"),
|
|
dbc.Button("Reset Zoom", id="reset-zoom-btn", color="secondary"),
|
|
])
|
|
])
|
|
], style={"height": "100vh"})
|
|
|
|
|
|
def create_chart_container():
|
|
"""
|
|
Create the main chart container for the 4-subplot layout.
|
|
|
|
Returns:
|
|
dash component: Chart container with 4-subplot layout
|
|
"""
|
|
return dcc.Graph(
|
|
id="main-charts",
|
|
figure=create_empty_subplot_layout(),
|
|
style={"height": "100vh"},
|
|
config={
|
|
"displayModeBar": True,
|
|
"displaylogo": False,
|
|
"modeBarButtonsToRemove": ["select2d", "lasso2d"],
|
|
"modeBarButtonsToAdd": ["resetScale2d"],
|
|
"scrollZoom": True, # Enable mouse wheel zooming
|
|
"doubleClick": "reset+autosize" # Double-click to reset zoom
|
|
}
|
|
)
|
|
|
|
|
|
def create_empty_subplot_layout():
|
|
"""
|
|
Create empty 4-subplot layout matching existing visualizer structure.
|
|
|
|
Returns:
|
|
plotly.graph_objects.Figure: Empty figure with 4 subplots
|
|
"""
|
|
fig = make_subplots(
|
|
rows=4, cols=1,
|
|
shared_xaxes=True,
|
|
subplot_titles=["OHLC", "Volume", "Order Book Imbalance (OBI)", "Cumulative Volume Delta (CVD)"],
|
|
vertical_spacing=0.02
|
|
)
|
|
|
|
# Configure layout to match existing styling
|
|
fig.update_layout(
|
|
height=800,
|
|
showlegend=False,
|
|
margin=dict(l=50, r=50, t=50, b=50),
|
|
template="plotly_dark", # Professional dark theme
|
|
paper_bgcolor='rgba(0,0,0,0)', # Transparent background
|
|
plot_bgcolor='rgba(0,0,0,0)' # Transparent plot area
|
|
)
|
|
|
|
# Configure synchronized zooming and panning
|
|
configure_synchronized_axes(fig)
|
|
|
|
return fig
|
|
|
|
|
|
def configure_synchronized_axes(fig):
|
|
"""
|
|
Configure synchronized zooming and panning across all subplots.
|
|
|
|
Args:
|
|
fig: Plotly figure with subplots
|
|
"""
|
|
# Enable dragmode for panning and zooming
|
|
fig.update_layout(
|
|
dragmode='zoom',
|
|
selectdirection='h' # Restrict selection to horizontal for time-based data
|
|
)
|
|
|
|
# Configure X-axes for synchronized behavior (already shared via make_subplots)
|
|
# All subplots will automatically share zoom/pan on X-axis due to shared_xaxes=True
|
|
|
|
# Configure individual Y-axes for better UX
|
|
fig.update_yaxes(fixedrange=False, gridcolor='rgba(128,128,128,0.2)') # Allow Y-axis zooming
|
|
fig.update_xaxes(fixedrange=False, gridcolor='rgba(128,128,128,0.2)') # Allow X-axis zooming
|
|
|
|
# Enable crosshair cursor spanning all charts
|
|
fig.update_layout(hovermode='x unified')
|
|
fig.update_traces(hovertemplate='<extra></extra>') # Clean hover labels
|
|
|
|
return fig
|
|
|
|
|
|
def add_ohlc_trace(fig, ohlc_data: dict):
|
|
"""
|
|
Add OHLC candlestick trace to the first subplot.
|
|
|
|
Args:
|
|
fig: Plotly figure with subplots
|
|
ohlc_data: Dict with x, open, high, low, close arrays
|
|
"""
|
|
candlestick = go.Candlestick(
|
|
x=ohlc_data["x"],
|
|
open=ohlc_data["open"],
|
|
high=ohlc_data["high"],
|
|
low=ohlc_data["low"],
|
|
close=ohlc_data["close"],
|
|
name="OHLC"
|
|
)
|
|
|
|
fig.add_trace(candlestick, row=1, col=1)
|
|
return fig
|
|
|
|
|
|
def add_volume_trace(fig, volume_data: dict):
|
|
"""
|
|
Add Volume bar trace to the second subplot.
|
|
|
|
Args:
|
|
fig: Plotly figure with subplots
|
|
volume_data: Dict with x (timestamps) and y (volumes) arrays
|
|
"""
|
|
volume_bar = go.Bar(
|
|
x=volume_data["x"],
|
|
y=volume_data["y"],
|
|
name="Volume",
|
|
marker_color='rgba(158, 185, 243, 0.7)', # Blue with transparency
|
|
showlegend=False,
|
|
hovertemplate="Volume: %{y}<extra></extra>"
|
|
)
|
|
|
|
fig.add_trace(volume_bar, row=2, col=1)
|
|
return fig
|
|
|
|
|
|
def add_obi_trace(fig, obi_data: dict):
|
|
"""
|
|
Add OBI line trace to the third subplot.
|
|
|
|
Args:
|
|
fig: Plotly figure with subplots
|
|
obi_data: Dict with timestamp and obi arrays
|
|
"""
|
|
obi_line = go.Scatter(
|
|
x=obi_data["timestamp"],
|
|
y=obi_data["obi"],
|
|
mode='lines',
|
|
name="OBI",
|
|
line=dict(color='blue', width=2),
|
|
showlegend=False,
|
|
hovertemplate="OBI: %{y:.3f}<extra></extra>"
|
|
)
|
|
|
|
# Add horizontal reference line at y=0
|
|
fig.add_hline(y=0, line=dict(color='gray', dash='dash', width=1), row=3, col=1)
|
|
fig.add_trace(obi_line, row=3, col=1)
|
|
return fig
|
|
|
|
|
|
def add_cvd_trace(fig, cvd_data: dict):
|
|
"""
|
|
Add CVD line trace to the fourth subplot.
|
|
|
|
Args:
|
|
fig: Plotly figure with subplots
|
|
cvd_data: Dict with timestamp and cvd arrays
|
|
"""
|
|
cvd_line = go.Scatter(
|
|
x=cvd_data["timestamp"],
|
|
y=cvd_data["cvd"],
|
|
mode='lines',
|
|
name="CVD",
|
|
line=dict(color='red', width=2),
|
|
showlegend=False,
|
|
hovertemplate="CVD: %{y:.1f}<extra></extra>"
|
|
)
|
|
|
|
fig.add_trace(cvd_line, row=4, col=1)
|
|
return fig
|
|
|
|
|
|
def create_populated_chart(ohlc_data, metrics_data):
|
|
"""
|
|
Create a chart container with real data populated.
|
|
|
|
Args:
|
|
ohlc_data: List of OHLC tuples or None
|
|
metrics_data: List of Metric objects or None
|
|
|
|
Returns:
|
|
dcc.Graph component with populated data
|
|
"""
|
|
from data_adapters import format_ohlc_for_plotly, format_volume_for_plotly, format_metrics_for_plotly
|
|
|
|
# Create base subplot layout
|
|
fig = create_empty_subplot_layout()
|
|
|
|
# Add real data if available
|
|
if ohlc_data:
|
|
# Format OHLC data
|
|
ohlc_formatted = format_ohlc_for_plotly(ohlc_data)
|
|
volume_formatted = format_volume_for_plotly(ohlc_data)
|
|
|
|
# Add OHLC trace
|
|
fig = add_ohlc_trace(fig, ohlc_formatted)
|
|
|
|
# Add Volume trace
|
|
fig = add_volume_trace(fig, volume_formatted)
|
|
|
|
if metrics_data:
|
|
# Format metrics data
|
|
metrics_formatted = format_metrics_for_plotly(metrics_data)
|
|
|
|
# Add OBI and CVD traces
|
|
if metrics_formatted["obi"]["x"]: # Check if we have OBI data
|
|
obi_data = {
|
|
"timestamp": metrics_formatted["obi"]["x"],
|
|
"obi": metrics_formatted["obi"]["y"]
|
|
}
|
|
fig = add_obi_trace(fig, obi_data)
|
|
if metrics_formatted["cvd"]["x"]: # Check if we have CVD data
|
|
cvd_data = {
|
|
"timestamp": metrics_formatted["cvd"]["x"],
|
|
"cvd": metrics_formatted["cvd"]["y"]
|
|
}
|
|
fig = add_cvd_trace(fig, cvd_data)
|
|
|
|
return dcc.Graph(
|
|
id="main-charts",
|
|
figure=fig,
|
|
style={"height": "100vh"},
|
|
config={
|
|
"displayModeBar": True,
|
|
"displaylogo": False,
|
|
"modeBarButtonsToRemove": ["select2d", "lasso2d"],
|
|
"modeBarButtonsToAdd": ["pan2d", "zoom2d", "zoomIn2d", "zoomOut2d", "resetScale2d"],
|
|
"scrollZoom": True,
|
|
"doubleClick": "reset+autosize"
|
|
}
|
|
)
|