캔들차트, 이동평균선, 일목균형표, 볼린저밴드 보이기 숨기기 기능적용

This commit is contained in:
Macbook
2026-06-03 00:19:44 +09:00
parent 08fb442d4a
commit 76e5b12d19
15 changed files with 427 additions and 69 deletions
+65 -55
View File
@@ -8,13 +8,10 @@ import { ChartManager } from '../utils/ChartManager';
import { getPaneHostId } from '../utils/indicatorPaneMerge';
import { isIndicatorPaneFocused, resolveFocusedPaneLayoutId } from '../utils/indicatorPaneFocus';
import { buildPaneItems, resolvePaneItemLayout } from './paneLegendLayout';
import { PAPER_OVERLAY_MENU_TITLE } from '../utils/paperChartOverlayVisibility';
/** 캔들 오버레이 표시 설정 그룹 메뉴 id */
export const CANDLE_OVERLAY_MENU_ID = '__candle_overlay_visibility__';
import type { ChartOverlayToggleKey } from '../utils/chartOverlayVisibility';
export interface CandleOverlayToggleItem {
key: string;
key: ChartOverlayToggleKey;
label: string;
visible: boolean;
}
@@ -26,7 +23,7 @@ export interface ChartRightToolbarProps {
paused?: boolean;
/** 캔들 pane 상단 — 이동평균·일목·볼린저 표시/숨김 (투자관리 차트 탭) */
candleOverlayToggles?: CandleOverlayToggleItem[];
onToggleCandleOverlay?: (key: string) => void;
onToggleCandleOverlay?: (key: ChartOverlayToggleKey) => void;
onSplit?: (hostId: string) => void;
onRemove?: (id: string) => void;
onDuplicate?: (id: string) => void;
@@ -107,6 +104,49 @@ const IconMore = () => (
</svg>
);
const IconMa = () => (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"
stroke="currentColor" strokeWidth="1.8" strokeLinecap="round">
<path d="M1 10 L5 4 L8 7 L13 2"/>
</svg>
);
const IconBollinger = () => (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"
stroke="currentColor" strokeWidth="1.4" strokeLinecap="round">
<path d="M1 4 Q7 1 13 4"/>
<path d="M1 7 Q7 4 13 7"/>
<path d="M1 10 Q7 13 13 10"/>
</svg>
);
const IconCandle = () => (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"
stroke="currentColor" strokeWidth="1.6" strokeLinecap="round">
<line x1="4" y1="2" x2="4" y2="12"/>
<rect x="2.5" y="5" width="3" height="4" rx="0.5" fill="currentColor" stroke="none"/>
<line x1="10" y1="3" x2="10" y2="11"/>
<rect x="8.5" y="4" width="3" height="5" rx="0.5" fill="currentColor" stroke="none"/>
</svg>
);
const IconIchimoku = () => (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"
stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round">
<path d="M1 9 L7 5 L13 9" fill="rgba(239,83,80,0.25)"/>
<path d="M1 5 L7 9 L13 5" fill="rgba(38,166,154,0.25)"/>
<path d="M1 9 L7 5 L13 9"/>
<path d="M1 5 L7 9 L13 5"/>
</svg>
);
const OVERLAY_ICONS: Record<ChartOverlayToggleKey, React.ReactNode> = {
ma: <IconMa />,
bollinger: <IconBollinger />,
ichimoku: <IconIchimoku />,
candle: <IconCandle />,
};
const IconEye = ({ hidden }: { hidden?: boolean }) => hidden ? (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.5">
<path d="M1 7 Q4 2.5 7 2.5 Q10 2.5 13 7 Q10 11.5 7 11.5 Q4 11.5 1 7 Z" strokeOpacity="0.35"/>
@@ -295,13 +335,12 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
const allowDrag = !!(onDragStart && paneItems.length > 1);
const clusterHeight = (allowDrag ? BTN_SIZE + BTN_GAP : 0) + BTN_SIZE;
const openItem = openMenuId && openMenuId !== CANDLE_OVERLAY_MENU_ID
const openItem = openMenuId
? paneItems.find(p => p.id === openMenuId)
: null;
const candlePane = paneLayouts.find(l => l.paneIndex === 0);
const showOverlayMenu = !!(candleOverlayToggles?.length && candlePane && onToggleCandleOverlay);
const overlayMenuOpen = openMenuId === CANDLE_OVERLAY_MENU_ID;
const showOverlayIcons = !!(candleOverlayToggles?.length && candlePane && onToggleCandleOverlay);
const focusedLayoutId = focusedId
? resolveFocusedPaneLayoutId(focusedId, indicators)
@@ -314,18 +353,6 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
})()
: null;
const overlayMenuItems: MenuItemDef[] = showOverlayMenu
? candleOverlayToggles!.map(item => ({
key: item.key,
title: item.visible ? `${item.label} 숨기기` : `${item.label} 표시`,
icon: <IconEye hidden={!item.visible} />,
active: !item.visible,
onClick: () => {
onToggleCandleOverlay?.(item.key);
},
}))
: [];
return (
<aside className="chart-right-toolbar" aria-label="차트 우측 도구">
<div
@@ -348,21 +375,26 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
</div>
)}
{showOverlayMenu && (
{showOverlayIcons && (
<div
className="chart-right-toolbar-cluster chart-right-toolbar-cluster--overlay"
className="chart-right-toolbar-cluster chart-right-toolbar-cluster--overlay-icons"
style={{ top: candlePane!.topY + 4 }}
>
<button
type="button"
className={`tv-side-btn tv-side-group-btn chart-right-toolbar-btn chart-right-toolbar-btn--menu${overlayMenuOpen ? ' active' : ''}`}
title="캔들 지표 표시 설정"
onMouseEnter={e => openFlyout(CANDLE_OVERLAY_MENU_ID, e.currentTarget)}
onMouseLeave={scheduleClose}
>
<IconMore />
<span className="tv-side-expand-arrow" />
</button>
{candleOverlayToggles!.map((item, idx) => (
<button
key={item.key}
type="button"
className={`tv-side-btn chart-right-toolbar-btn chart-right-toolbar-btn--overlay${item.visible ? '' : ' chart-right-toolbar-btn--overlay-off'}`}
title={item.visible ? `${item.label} 숨기기` : `${item.label} 표시`}
style={{ marginTop: idx > 0 ? BTN_GAP : 0 }}
onClick={e => {
e.stopPropagation();
onToggleCandleOverlay?.(item.key);
}}
>
{OVERLAY_ICONS[item.key]}
</button>
))}
</div>
)}
@@ -425,28 +457,6 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
})}
</div>
{overlayMenuOpen && overlayMenuItems.length > 0 && (
<div
className="tv-side-flyout tv-side-flyout--right-anchor"
style={{ top: flyoutPos.top, left: flyoutPos.left }}
onMouseEnter={cancelClose}
onMouseLeave={scheduleClose}
>
<div className="tv-side-flyout-header">{PAPER_OVERLAY_MENU_TITLE}</div>
{overlayMenuItems.map(mi => (
<button
key={mi.key}
type="button"
className={`tv-side-flyout-item${mi.active ? ' active' : ''}`}
onClick={e => { e.stopPropagation(); mi.onClick(); }}
>
<span className="tv-side-flyout-icon">{mi.icon}</span>
<span className="tv-side-flyout-title">{mi.title}</span>
</button>
))}
</div>
)}
{openItem && openMenuId && (() => {
const menuItems = buildMenuItems(openItem, indicators, paneItems, {
focusedId, onSplit, onDuplicate, onExpand, onRestore, onRemove,