feat: proper time-based X-axis with angled hour labels and bold dates
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/build-1 Pipeline failed
ci/woodpecker/push/build-3 Pipeline failed
ci/woodpecker/push/build-2 Pipeline failed
ci/woodpecker/push/finalize unknown status
Build and Push / lint-and-test (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.adapters.broker_adapter name:broker-adapter]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.aggregation.worker name:aggregation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.extractor.worker name:extractor]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.ingestion.worker name:ingestion]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.lake_publisher.worker name:lake-publisher]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.parser.worker name:parser]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.recommendation.worker name:recommendation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.scheduler.app name:scheduler]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.api.app:app --host 0.0.0.0 --port 8000 name:query-api]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.risk.app:app --host 0.0.0.0 --port 8000 name:risk]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.symbol_registry.app:app --host 0.0.0.0 --port 8000 name:symbol-registry]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.trading.app:app --host 0.0.0.0 --port 8000 name:trading-engine]) (push) Has been cancelled
Build and Push / build-dashboard (push) Has been cancelled
Build and Push / build-superset (push) Has been cancelled
Build and Push / integration-test (push) Has been cancelled
Build and Push / beta-gate (push) Has been cancelled
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/build-1 Pipeline failed
ci/woodpecker/push/build-3 Pipeline failed
ci/woodpecker/push/build-2 Pipeline failed
ci/woodpecker/push/finalize unknown status
Build and Push / lint-and-test (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.adapters.broker_adapter name:broker-adapter]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.aggregation.worker name:aggregation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.extractor.worker name:extractor]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.ingestion.worker name:ingestion]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.lake_publisher.worker name:lake-publisher]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.parser.worker name:parser]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.recommendation.worker name:recommendation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.scheduler.app name:scheduler]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.api.app:app --host 0.0.0.0 --port 8000 name:query-api]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.risk.app:app --host 0.0.0.0 --port 8000 name:risk]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.symbol_registry.app:app --host 0.0.0.0 --port 8000 name:symbol-registry]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.trading.app:app --host 0.0.0.0 --port 8000 name:trading-engine]) (push) Has been cancelled
Build and Push / build-dashboard (push) Has been cancelled
Build and Push / build-superset (push) Has been cancelled
Build and Push / integration-test (push) Has been cancelled
Build and Push / beta-gate (push) Has been cancelled
Replaced string-based X-axis with numeric timestamp axis: - Custom ChartXTick component renders hour marks at -35° angle - New day boundaries shown in bold (e.g., 'Apr 29') - Hour marks shown as '9:00 AM', '10:00 AM' etc. - Tooltip shows full date+time on hover - Direction timeline uses formatted timestamps - Bottom margin increased to accommodate angled labels
This commit is contained in:
@@ -602,13 +602,46 @@ interface ChartPoint {
|
||||
price?: number;
|
||||
}
|
||||
|
||||
function ChartXTick({ x, y, payload }: { x?: number; y?: number; payload?: { value: number } }) {
|
||||
if (!payload || !x || !y) return null;
|
||||
const d = new Date(payload.value);
|
||||
const hour = d.getHours();
|
||||
const minute = d.getMinutes();
|
||||
const isNewDay = hour === 0 && minute < 30;
|
||||
const isHourMark = minute < 15; // Show only on-the-hour ticks
|
||||
|
||||
// For date labels (new day boundary)
|
||||
if (isNewDay) {
|
||||
const label = d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
||||
return (
|
||||
<g transform={`translate(${x},${y})`}>
|
||||
<text x={0} y={12} textAnchor="middle" fill="#e2e8f0" fontSize={11} fontWeight="bold">
|
||||
{label}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
// For time labels — show rounded hours at an angle
|
||||
if (!isHourMark) return null;
|
||||
const timeStr = d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
|
||||
return (
|
||||
<g transform={`translate(${x},${y})`}>
|
||||
<text x={0} y={8} textAnchor="end" fill="#94a3b8" fontSize={10} transform="rotate(-35)">
|
||||
{timeStr}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
function TrendTooltip({ active, payload, label }: Record<string, unknown>) {
|
||||
if (!active) return null;
|
||||
const items = payload as Array<{ name: string; value: number; color: string; dataKey: string }> | undefined;
|
||||
if (!items?.length) return null;
|
||||
const ts = typeof label === 'number' ? new Date(label).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }) : String(label ?? '');
|
||||
return (
|
||||
<div className="rounded-lg border border-surface-700 bg-surface-900 px-3 py-2 text-xs shadow-lg">
|
||||
<div className="mb-1 text-gray-400">{String(label ?? '')}</div>
|
||||
<div className="mb-1 text-gray-400">{ts}</div>
|
||||
{items.map((item, i) => (
|
||||
<div key={i} className="flex justify-between gap-4" style={{ color: item.color }}>
|
||||
<span>{item.name}:</span>
|
||||
@@ -709,7 +742,7 @@ function TrendHistoryChart({ trends, latestTrends, ticker, marketPrices, selecte
|
||||
const trendTs = new Date(t.generated_at).getTime();
|
||||
const price = findClosestPrice(trendTs);
|
||||
return {
|
||||
time: new Date(t.generated_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }),
|
||||
time: String(trendTs),
|
||||
timestamp: trendTs,
|
||||
strength: +(t.trend_strength * 100).toFixed(1),
|
||||
confidence: +(t.confidence * 100).toFixed(1),
|
||||
@@ -766,12 +799,16 @@ function TrendHistoryChart({ trends, latestTrends, ticker, marketPrices, selecte
|
||||
Trend Strength & Confidence — {ticker} / {selectedWindow}
|
||||
</h2>
|
||||
<ResponsiveContainer width="100%" height={280}>
|
||||
<LineChart data={chartData} margin={{ top: 5, right: 20, bottom: 5, left: 0 }}>
|
||||
<LineChart data={chartData} margin={{ top: 5, right: 20, bottom: 40, left: 0 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
tick={{ fill: '#94a3b8', fontSize: 11 }}
|
||||
dataKey="timestamp"
|
||||
type="number"
|
||||
domain={['dataMin', 'dataMax']}
|
||||
scale="time"
|
||||
tick={<ChartXTick />}
|
||||
tickLine={{ stroke: '#475569' }}
|
||||
interval="preserveStartEnd"
|
||||
/>
|
||||
<YAxis
|
||||
yAxisId="left"
|
||||
@@ -853,13 +890,13 @@ function TrendHistoryChart({ trends, latestTrends, ticker, marketPrices, selecte
|
||||
'bg-gray-600';
|
||||
const height = Math.max(8, pt.strength * 0.5);
|
||||
return (
|
||||
<div key={i} className="flex flex-col items-center gap-1" title={`${pt.time}: ${pt.directionLabel} (${pt.strength}%)`}>
|
||||
<div key={i} className="flex flex-col items-center gap-1" title={`${new Date(pt.timestamp).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })}: ${pt.directionLabel} (${pt.strength}%)`}>
|
||||
<div
|
||||
className={`w-3 rounded-sm ${color}`}
|
||||
style={{ height: `${height}px` }}
|
||||
/>
|
||||
{i % Math.max(1, Math.floor(chartData.length / 8)) === 0 && (
|
||||
<span className="text-[9px] text-gray-500 -rotate-45 origin-top-left whitespace-nowrap">{pt.time}</span>
|
||||
<span className="text-[9px] text-gray-500 -rotate-45 origin-top-left whitespace-nowrap">{new Date(pt.timestamp).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user