148 lines
3.9 KiB
TypeScript
148 lines
3.9 KiB
TypeScript
import React, { useRef, useEffect } from 'react';
|
|
import { Box } from '@mui/material';
|
|
|
|
export interface FibonacciFanObject {
|
|
id: string;
|
|
type: 'fibonacciFan';
|
|
startPrice: number;
|
|
endPrice: number;
|
|
startTime: number;
|
|
endTime: number;
|
|
settings: {
|
|
color: string;
|
|
lineWidth: number;
|
|
lineStyle: 'solid' | 'dashed' | 'dotted';
|
|
levels: number[];
|
|
showLabels: boolean;
|
|
};
|
|
}
|
|
|
|
interface FibonacciFanProps {
|
|
objects: FibonacciFanObject[];
|
|
chartWidth: number;
|
|
chartHeight: number;
|
|
totalChartHeight?: number;
|
|
priceScale: {
|
|
from: number;
|
|
to: number;
|
|
};
|
|
timeScale: {
|
|
from: number;
|
|
to: number;
|
|
};
|
|
candleData: Array<{ time: string | number; open: number; high: number; low: number; close: number }>;
|
|
timeToX: (time: number) => number | null;
|
|
priceToY: (price: number) => number | null;
|
|
onClick?: (objectId: string, event: React.MouseEvent) => void;
|
|
onDoubleClick?: (objectId: string) => void;
|
|
onDragStart?: (objectId: string, startX: number, startTime: number) => void;
|
|
}
|
|
|
|
const FibonacciFan: React.FC<FibonacciFanProps> = ({
|
|
objects,
|
|
chartWidth,
|
|
chartHeight,
|
|
totalChartHeight,
|
|
priceScale,
|
|
timeScale,
|
|
candleData,
|
|
timeToX,
|
|
priceToY,
|
|
onClick,
|
|
onDoubleClick,
|
|
onDragStart,
|
|
}) => {
|
|
const svgRef = useRef<SVGSVGElement>(null);
|
|
|
|
const calculateFanLines = (startPrice: number, endPrice: number) => {
|
|
const priceRange = endPrice - startPrice;
|
|
const levels = [0.382, 0.5, 0.618]; // 기본 피보나치 팬 레벨
|
|
|
|
return levels.map(level => ({
|
|
level,
|
|
price: startPrice + (priceRange * level)
|
|
}));
|
|
};
|
|
|
|
const renderFan = (fanObject: FibonacciFanObject) => {
|
|
const startX = timeToX(fanObject.startTime);
|
|
const endX = timeToX(fanObject.endTime);
|
|
const startY = priceToY(fanObject.startPrice);
|
|
|
|
if (startX === null || endX === null || startY === null) return null;
|
|
|
|
const fanLines = calculateFanLines(fanObject.startPrice, fanObject.endPrice);
|
|
|
|
return (
|
|
<g key={fanObject.id}>
|
|
{fanLines.map((fanLine, index) => {
|
|
const endY = priceToY(fanLine.price);
|
|
if (endY === null) return null;
|
|
|
|
const strokeDasharray = fanObject.settings.lineStyle === 'dashed'
|
|
? '5,5'
|
|
: fanObject.settings.lineStyle === 'dotted'
|
|
? '2,2'
|
|
: undefined;
|
|
|
|
return (
|
|
<g key={index}>
|
|
<line
|
|
x1={startX}
|
|
y1={startY}
|
|
x2={endX}
|
|
y2={endY}
|
|
stroke={fanObject.settings.color}
|
|
strokeWidth={fanObject.settings.lineWidth}
|
|
strokeDasharray={strokeDasharray}
|
|
opacity={0.8}
|
|
style={{ cursor: 'pointer' }}
|
|
onClick={(e) => onClick?.(fanObject.id, e)}
|
|
onDoubleClick={() => onDoubleClick?.(fanObject.id)}
|
|
/>
|
|
{fanObject.settings.showLabels && (
|
|
<text
|
|
x={endX + 5}
|
|
y={endY}
|
|
fill={fanObject.settings.color}
|
|
fontSize={10}
|
|
fontFamily="Arial, sans-serif"
|
|
>
|
|
{`${(fanLine.level * 100).toFixed(1)}%`}
|
|
</text>
|
|
)}
|
|
</g>
|
|
);
|
|
})}
|
|
</g>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Box
|
|
sx={{
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
width: chartWidth,
|
|
height: totalChartHeight || chartHeight,
|
|
pointerEvents: 'none',
|
|
zIndex: 100,
|
|
}}
|
|
>
|
|
<svg
|
|
ref={svgRef}
|
|
width={chartWidth}
|
|
height={totalChartHeight || chartHeight}
|
|
style={{ position: 'absolute', top: 0, left: 0 }}
|
|
>
|
|
<g style={{ pointerEvents: 'all' }}>
|
|
{objects.map(renderFan)}
|
|
</g>
|
|
</svg>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default FibonacciFan;
|