Extract magic numbers and colors to named constants in CandleChart.tsx
Replace inline magic numbers (8px, 60s, 2, 1, 0.15, 0.25, 100) and hardcoded color strings with named module-level constants for better maintainability and clarity. Organized constants into logical groups: - Magic number constants (tolerance, intervals, sizes, alphas) - Dark theme colors - Light theme colors - Candlestick colors - Prediction overlay colors - Predefined label colors This improves code readability and makes it easier to adjust UI parameters globally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d6d844a003
commit
36182986b6
1 changed files with 100 additions and 61 deletions
|
|
@ -8,6 +8,57 @@ import { TrendLine } from '@/plugins/trend-line';
|
|||
import { RectangleDrawingPrimitive, RectanglePoint } from '@/plugins/rectangle-drawing';
|
||||
import type { PerCandlePrediction, PredictionSpan, ModelInfoResponse, PredictionSummary } from '@/types/predictions';
|
||||
|
||||
// === Magic Number Constants ===
|
||||
const ENDPOINT_CLICK_TOLERANCE_PX = 8;
|
||||
const DEFAULT_CANDLE_INTERVAL_S = 60;
|
||||
const MARKER_TIME_TOLERANCE_S = 60;
|
||||
const LINE_WIDTH = 2;
|
||||
const MARKER_SIZE_DEFAULT = 1;
|
||||
const MARKER_SIZE_SELECTED = 2;
|
||||
const PREDICTION_HISTOGRAM_ALPHA_DEFAULT = 0.15;
|
||||
const PREDICTION_HISTOGRAM_ALPHA_DISAGREEMENT = 0.25;
|
||||
const CONFIDENCE_PERCENTAGE_MULTIPLIER = 100;
|
||||
|
||||
// === Color Constants - Dark Theme ===
|
||||
const DARK_BG_COLOR = '#000000';
|
||||
const DARK_TEXT_COLOR = '#00ff41';
|
||||
const DARK_GRID_COLOR = '#003311';
|
||||
const DARK_BORDER_COLOR = '#003311';
|
||||
const DARK_CROSSHAIR_COLOR = '#00ff41';
|
||||
|
||||
// === Color Constants - Light Theme ===
|
||||
const LIGHT_BG_COLOR = '#ffffff';
|
||||
const LIGHT_TEXT_COLOR = '#1a1a2e';
|
||||
const LIGHT_GRID_COLOR = '#d1d5db';
|
||||
const LIGHT_BORDER_COLOR = '#d1d5db';
|
||||
const LIGHT_CROSSHAIR_COLOR = '#16a34a';
|
||||
|
||||
// === Color Constants - Candlestick ===
|
||||
const CANDLESTICK_UP_COLOR = '#ffffff';
|
||||
const CANDLESTICK_DOWN_COLOR = '#444444';
|
||||
const CANDLESTICK_BORDER_UP_COLOR = '#444444';
|
||||
const CANDLESTICK_BORDER_DOWN_COLOR = '#444444';
|
||||
const CANDLESTICK_WICK_UP_COLOR = '#444444';
|
||||
const CANDLESTICK_WICK_DOWN_COLOR = '#444444';
|
||||
|
||||
// === Color Constants - Prediction ===
|
||||
const PREDICTION_HISTOGRAM_DEFAULT_COLOR = 'rgba(128, 128, 128, 0.15)';
|
||||
const PREDICTION_DEFAULT_FALLBACK_COLOR = '#888888';
|
||||
const PREDICTION_MISSED_BY_HUMAN_COLOR = '#eab308';
|
||||
const PREDICTION_LABEL_MISMATCH_COLOR = '#f97316';
|
||||
|
||||
// === Predefined Label Colors ===
|
||||
const PREDEFINED_LABEL_COLORS = [
|
||||
'#3b82f6', // blue
|
||||
'#ef4444', // red
|
||||
'#10b981', // green
|
||||
'#f59e0b', // amber
|
||||
'#8b5cf6', // violet
|
||||
'#ec4899', // pink
|
||||
'#06b6d4', // cyan
|
||||
'#f97316', // orange
|
||||
];
|
||||
|
||||
interface DrawingState {
|
||||
tool: 'line' | 'rectangle';
|
||||
firstPoint: { time: Time; price: number };
|
||||
|
|
@ -242,41 +293,41 @@ const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
|
|||
if (isDark) {
|
||||
return {
|
||||
layout: {
|
||||
background: { color: '#000000' },
|
||||
textColor: '#00ff41',
|
||||
background: { color: DARK_BG_COLOR },
|
||||
textColor: DARK_TEXT_COLOR,
|
||||
},
|
||||
grid: {
|
||||
vertLines: { color: '#003311' },
|
||||
horzLines: { color: '#003311' },
|
||||
vertLines: { color: DARK_GRID_COLOR },
|
||||
horzLines: { color: DARK_GRID_COLOR },
|
||||
},
|
||||
timeScale: {
|
||||
borderColor: '#003311',
|
||||
borderColor: DARK_BORDER_COLOR,
|
||||
},
|
||||
rightPriceScale: {
|
||||
borderColor: '#003311',
|
||||
borderColor: DARK_BORDER_COLOR,
|
||||
},
|
||||
crosshair: {
|
||||
color: '#00ff41',
|
||||
color: DARK_CROSSHAIR_COLOR,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
layout: {
|
||||
background: { color: '#ffffff' },
|
||||
textColor: '#1a1a2e',
|
||||
background: { color: LIGHT_BG_COLOR },
|
||||
textColor: LIGHT_TEXT_COLOR,
|
||||
},
|
||||
grid: {
|
||||
vertLines: { color: '#d1d5db' },
|
||||
horzLines: { color: '#d1d5db' },
|
||||
vertLines: { color: LIGHT_GRID_COLOR },
|
||||
horzLines: { color: LIGHT_GRID_COLOR },
|
||||
},
|
||||
timeScale: {
|
||||
borderColor: '#d1d5db',
|
||||
borderColor: LIGHT_BORDER_COLOR,
|
||||
},
|
||||
rightPriceScale: {
|
||||
borderColor: '#d1d5db',
|
||||
borderColor: LIGHT_BORDER_COLOR,
|
||||
},
|
||||
crosshair: {
|
||||
color: '#16a34a',
|
||||
color: LIGHT_CROSSHAIR_COLOR,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -307,18 +358,18 @@ const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
|
|||
});
|
||||
|
||||
const candlestickSeries = chart.addCandlestickSeries({
|
||||
upColor: '#ffffff',
|
||||
downColor: '#444444',
|
||||
upColor: CANDLESTICK_UP_COLOR,
|
||||
downColor: CANDLESTICK_DOWN_COLOR,
|
||||
borderVisible: true,
|
||||
wickUpColor: '#444444',
|
||||
wickDownColor: '#444444',
|
||||
borderUpColor: '#444444',
|
||||
borderDownColor: '#444444',
|
||||
wickUpColor: CANDLESTICK_WICK_UP_COLOR,
|
||||
wickDownColor: CANDLESTICK_WICK_DOWN_COLOR,
|
||||
borderUpColor: CANDLESTICK_BORDER_UP_COLOR,
|
||||
borderDownColor: CANDLESTICK_BORDER_DOWN_COLOR,
|
||||
});
|
||||
|
||||
// Add histogram series for prediction overlay (rendered behind candles)
|
||||
const histogramSeries = chart.addHistogramSeries({
|
||||
color: 'rgba(128, 128, 128, 0.15)',
|
||||
color: PREDICTION_HISTOGRAM_DEFAULT_COLOR,
|
||||
priceFormat: { type: 'volume' },
|
||||
priceScaleId: 'prediction_overlay',
|
||||
});
|
||||
|
|
@ -407,7 +458,7 @@ const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
|
|||
: annotationType.color,
|
||||
shape: isUpArrow ? ('arrowUp' as const) : ('arrowDown' as const),
|
||||
text: annotationType.display_name,
|
||||
size: isSelected ? 2 : 1,
|
||||
size: isSelected ? MARKER_SIZE_SELECTED : MARKER_SIZE_DEFAULT,
|
||||
};
|
||||
})
|
||||
.filter((m) => m !== null)
|
||||
|
|
@ -431,18 +482,8 @@ const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
|
|||
// Generate colors for labels since backend no longer provides them
|
||||
const labelColorMap: Record<string, string> = {};
|
||||
if (modelInfo?.labels) {
|
||||
const predefinedColors = [
|
||||
'#3b82f6', // blue
|
||||
'#ef4444', // red
|
||||
'#10b981', // green
|
||||
'#f59e0b', // amber
|
||||
'#8b5cf6', // violet
|
||||
'#ec4899', // pink
|
||||
'#06b6d4', // cyan
|
||||
'#f97316', // orange
|
||||
];
|
||||
modelInfo.labels.forEach((label, index) => {
|
||||
labelColorMap[label] = predefinedColors[index % predefinedColors.length];
|
||||
labelColorMap[label] = PREDEFINED_LABEL_COLORS[index % PREDEFINED_LABEL_COLORS.length];
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -473,7 +514,7 @@ const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
|
|||
const candleMap = new Map(candles.map((c) => [c.time, c]));
|
||||
|
||||
// Detect candle interval from data (fall back to 60s if fewer than 2 candles)
|
||||
const candleInterval = candles.length >= 2 ? candles[1].time - candles[0].time : 60;
|
||||
const candleInterval = candles.length >= 2 ? candles[1].time - candles[0].time : DEFAULT_CANDLE_INTERVAL_S;
|
||||
|
||||
// Build a map from prediction time to its span for disagreement lookup
|
||||
const predictionTimeToSpan = new Map<number, PredictionSpan>();
|
||||
|
|
@ -508,25 +549,25 @@ const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
|
|||
const candle = candleMap.get(p.time);
|
||||
// Use candle high as the histogram value so it overlays correctly
|
||||
const value = candle ? candle.high : 0;
|
||||
|
||||
|
||||
// Check if this prediction is part of a disagreement
|
||||
const span = predictionTimeToSpan.get(p.time);
|
||||
const disagreementType = span ? disagreementMap.get(span.start_time) : null;
|
||||
|
||||
let baseColor = labelColorMap[p.label] || '#888888';
|
||||
let alpha = 0.15;
|
||||
|
||||
|
||||
let baseColor = labelColorMap[p.label] || PREDICTION_DEFAULT_FALLBACK_COLOR;
|
||||
let alpha = PREDICTION_HISTOGRAM_ALPHA_DEFAULT;
|
||||
|
||||
// Apply disagreement-specific colors and styling
|
||||
if (disagreementType === 'missed_by_human') {
|
||||
// Yellow highlight for predictions missed by humans
|
||||
baseColor = '#eab308'; // yellow
|
||||
alpha = 0.25;
|
||||
baseColor = PREDICTION_MISSED_BY_HUMAN_COLOR;
|
||||
alpha = PREDICTION_HISTOGRAM_ALPHA_DISAGREEMENT;
|
||||
} else if (disagreementType === 'label_mismatch') {
|
||||
// Orange for label mismatches
|
||||
baseColor = '#f97316'; // orange
|
||||
alpha = 0.25;
|
||||
baseColor = PREDICTION_LABEL_MISMATCH_COLOR;
|
||||
alpha = PREDICTION_HISTOGRAM_ALPHA_DISAGREEMENT;
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
time: p.time as Time,
|
||||
value,
|
||||
|
|
@ -554,26 +595,26 @@ const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
|
|||
})
|
||||
.map((span) => {
|
||||
const disagreementType = disagreementMap.get(span.start_time);
|
||||
let baseColor = labelColorMap[span.label] || '#888888';
|
||||
let baseColor = labelColorMap[span.label] || PREDICTION_DEFAULT_FALLBACK_COLOR;
|
||||
let labelText = span.label;
|
||||
|
||||
|
||||
// Apply disagreement-specific styling to markers
|
||||
if (disagreementType === 'missed_by_human') {
|
||||
baseColor = '#eab308'; // yellow
|
||||
baseColor = PREDICTION_MISSED_BY_HUMAN_COLOR;
|
||||
labelText = `⚠ ${span.label}`;
|
||||
} else if (disagreementType === 'label_mismatch') {
|
||||
baseColor = '#f97316'; // orange
|
||||
baseColor = PREDICTION_LABEL_MISMATCH_COLOR;
|
||||
labelText = `⚠ ${span.label}`;
|
||||
}
|
||||
|
||||
const confidencePct = Math.round(span.avg_confidence * 100);
|
||||
|
||||
const confidencePct = Math.round(span.avg_confidence * CONFIDENCE_PERCENTAGE_MULTIPLIER);
|
||||
return {
|
||||
time: span.start_time as Time,
|
||||
position: 'belowBar' as const,
|
||||
color: baseColor,
|
||||
shape: 'square' as const,
|
||||
text: `${labelText} (${confidencePct}%)`,
|
||||
size: 1,
|
||||
size: MARKER_SIZE_DEFAULT,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => (a.time as number) - (b.time as number));
|
||||
|
|
@ -589,14 +630,14 @@ const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
|
|||
const isNearEndpoint = (clickX: number, clickY: number, endpointTime: Time, endpointPrice: number): boolean => {
|
||||
const endpointX = chartRef.current!.timeScale().timeToCoordinate(endpointTime);
|
||||
const endpointY = seriesRef.current!.priceToCoordinate(endpointPrice);
|
||||
|
||||
|
||||
if (endpointX === null || endpointY === null) return false;
|
||||
|
||||
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(clickX - endpointX, 2) + Math.pow(clickY - endpointY, 2)
|
||||
);
|
||||
|
||||
return distance <= 8; // 8 pixel tolerance for endpoint handles
|
||||
|
||||
return distance <= ENDPOINT_CLICK_TOLERANCE_PX;
|
||||
};
|
||||
|
||||
const handleClick = async (param: any) => {
|
||||
|
|
@ -682,7 +723,7 @@ const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
|
|||
previewPoint as Point,
|
||||
{
|
||||
lineColor: selectedColor,
|
||||
width: 2,
|
||||
width: LINE_WIDTH,
|
||||
showLabels: false,
|
||||
isPreview: true,
|
||||
}
|
||||
|
|
@ -865,11 +906,10 @@ const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
|
|||
.map((t) => t.name);
|
||||
|
||||
// Find annotation at this timestamp (within tolerance)
|
||||
const tolerance = 60; // 60 seconds tolerance
|
||||
const annotation = annotationsRef.current.find(
|
||||
(a) =>
|
||||
markerTypeNames.includes(a.label_type) &&
|
||||
Math.abs(a.timestamp - timestamp) < tolerance
|
||||
Math.abs(a.timestamp - timestamp) < MARKER_TIME_TOLERANCE_S
|
||||
);
|
||||
|
||||
if (annotation) {
|
||||
|
|
@ -982,11 +1022,10 @@ const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
|
|||
.map((t) => t.name);
|
||||
|
||||
// Find annotation at this timestamp (within tolerance)
|
||||
const tolerance = 60; // 60 seconds tolerance
|
||||
const annotation = annotationsRef.current.find(
|
||||
(a) =>
|
||||
markerTypeNames.includes(a.label_type) &&
|
||||
Math.abs(a.timestamp - timestamp) < tolerance
|
||||
Math.abs(a.timestamp - timestamp) < MARKER_TIME_TOLERANCE_S
|
||||
);
|
||||
|
||||
if (annotation) {
|
||||
|
|
@ -1128,7 +1167,7 @@ const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
|
|||
p2,
|
||||
{
|
||||
lineColor: color,
|
||||
width: 2,
|
||||
width: LINE_WIDTH,
|
||||
showLabels: false,
|
||||
annotationId: String(annotation.id),
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue