goldenChat base source add
This commit is contained in:
@@ -0,0 +1,597 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
Button,
|
||||
Box,
|
||||
Typography,
|
||||
TextField,
|
||||
Switch,
|
||||
Slider,
|
||||
Divider,
|
||||
Paper,
|
||||
Grid,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Alert,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
} from '@mui/material';
|
||||
import { Close, Refresh } from '@mui/icons-material';
|
||||
import { useIndicatorVisibility } from '../contexts/IndicatorVisibilityContext';
|
||||
import { useChartColors, ChartColorSettings } from '../contexts/ChartColorContext';
|
||||
import type { MultiChartBollingerState } from '../stores/useDashboardStore';
|
||||
import {
|
||||
bollingerDialogDefaultsFromStore,
|
||||
bollingerDialogToStore,
|
||||
bollingerStoreToDialogColors,
|
||||
bollingerStoreToDialogSettings,
|
||||
} from '../utils/multiChartToolbarSettings';
|
||||
import DraggablePaper from './DraggablePaper';
|
||||
|
||||
interface BollingerSettingsDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
multiChartToolbar?: {
|
||||
bollinger: MultiChartBollingerState;
|
||||
setBollinger: (
|
||||
value: MultiChartBollingerState | ((prev: MultiChartBollingerState) => MultiChartBollingerState)
|
||||
) => void;
|
||||
};
|
||||
}
|
||||
|
||||
const BollingerSettingsDialog: React.FC<BollingerSettingsDialogProps> = ({ open, onClose, multiChartToolbar }) => {
|
||||
const { settings, updateSettings } = useIndicatorVisibility();
|
||||
const { colors, updateColors } = useChartColors();
|
||||
|
||||
// 로컬 상태 (볼린저 밴드 관련 설정만 포함)
|
||||
const [localSettings, setLocalSettings] = useState<{
|
||||
bollingerUpper: boolean;
|
||||
bollingerMiddle: boolean;
|
||||
bollingerLower: boolean;
|
||||
bollingerPeriod: number;
|
||||
bollingerStdDev: number;
|
||||
}>({
|
||||
bollingerUpper: settings.bollingerUpper,
|
||||
bollingerMiddle: settings.bollingerMiddle,
|
||||
bollingerLower: settings.bollingerLower,
|
||||
bollingerPeriod: settings.bollingerPeriod || 20,
|
||||
bollingerStdDev: settings.bollingerStdDev || 2.0,
|
||||
});
|
||||
|
||||
const [localColors, setLocalColors] = useState({
|
||||
bollingerUpperColor: colors.bollingerUpperColor,
|
||||
bollingerMiddleColor: colors.bollingerMiddleColor,
|
||||
bollingerLowerColor: colors.bollingerLowerColor,
|
||||
bollingerUpperWidth: colors.bollingerUpperWidth,
|
||||
bollingerMiddleWidth: colors.bollingerMiddleWidth,
|
||||
bollingerLowerWidth: colors.bollingerLowerWidth,
|
||||
bollingerUpperLineStyle: colors.bollingerUpperLineStyle,
|
||||
bollingerMiddleLineStyle: colors.bollingerMiddleLineStyle,
|
||||
bollingerLowerLineStyle: colors.bollingerLowerLineStyle,
|
||||
bollingerBandBackgroundColor: colors.bollingerBandBackgroundColor,
|
||||
});
|
||||
|
||||
// 배경 투명도 상태
|
||||
const [bandOpacity, setBandOpacity] = useState<number>(0.15); // ✅ 기본값 증가 (더 잘 보이도록)
|
||||
|
||||
// rgba에서 alpha 값 추출 함수
|
||||
const extractOpacity = (color: string): number => {
|
||||
const match = color.match(/rgba?\([^,]+,[^,]+,[^,]+,\s*([\d.]+)\)/);
|
||||
return match ? parseFloat(match[1]) : 0.1;
|
||||
};
|
||||
|
||||
// rgba에서 rgb hex 추출 함수
|
||||
const extractRgbFromRgba = (rgba: string): string => {
|
||||
const match = rgba.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
||||
if (match) {
|
||||
const r = parseInt(match[1]).toString(16).padStart(2, '0');
|
||||
const g = parseInt(match[2]).toString(16).padStart(2, '0');
|
||||
const b = parseInt(match[3]).toString(16).padStart(2, '0');
|
||||
return `#${r}${g}${b}`;
|
||||
}
|
||||
return '#000000';
|
||||
};
|
||||
|
||||
// hex to rgba 변환 함수
|
||||
const hexToRgba = (hex: string, opacity: number): string => {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
if (result) {
|
||||
const r = parseInt(result[1], 16);
|
||||
const g = parseInt(result[2], 16);
|
||||
const b = parseInt(result[3], 16);
|
||||
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
||||
}
|
||||
return `rgba(0, 0, 0, ${opacity})`;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !multiChartToolbar) return;
|
||||
const b = multiChartToolbar.bollinger;
|
||||
setLocalSettings(bollingerStoreToDialogSettings(b));
|
||||
setLocalColors(bollingerStoreToDialogColors(b));
|
||||
setBandOpacity(extractOpacity(b.bandBackgroundColor));
|
||||
}, [open, multiChartToolbar]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || multiChartToolbar) return;
|
||||
setLocalSettings({
|
||||
bollingerUpper: settings.bollingerUpper,
|
||||
bollingerMiddle: settings.bollingerMiddle,
|
||||
bollingerLower: settings.bollingerLower,
|
||||
bollingerPeriod: settings.bollingerPeriod || 20,
|
||||
bollingerStdDev: settings.bollingerStdDev || 2.0,
|
||||
});
|
||||
setLocalColors({
|
||||
bollingerUpperColor: colors.bollingerUpperColor,
|
||||
bollingerMiddleColor: colors.bollingerMiddleColor,
|
||||
bollingerLowerColor: colors.bollingerLowerColor,
|
||||
bollingerUpperWidth: colors.bollingerUpperWidth,
|
||||
bollingerMiddleWidth: colors.bollingerMiddleWidth,
|
||||
bollingerLowerWidth: colors.bollingerLowerWidth,
|
||||
bollingerBandBackgroundColor: colors.bollingerBandBackgroundColor,
|
||||
bollingerUpperLineStyle: colors.bollingerUpperLineStyle,
|
||||
bollingerMiddleLineStyle: colors.bollingerMiddleLineStyle,
|
||||
bollingerLowerLineStyle: colors.bollingerLowerLineStyle,
|
||||
});
|
||||
setBandOpacity(extractOpacity(colors.bollingerBandBackgroundColor));
|
||||
}, [open, multiChartToolbar, settings, colors]);
|
||||
|
||||
const handleSave = () => {
|
||||
if (multiChartToolbar) {
|
||||
multiChartToolbar.setBollinger(bollingerDialogToStore(localSettings, localColors));
|
||||
} else {
|
||||
console.log('💾 [볼린저밴드 설정] 저장 시작:', {
|
||||
이전설정: {
|
||||
period: settings.bollingerPeriod,
|
||||
stdDev: settings.bollingerStdDev,
|
||||
upper: settings.bollingerUpper,
|
||||
middle: settings.bollingerMiddle,
|
||||
lower: settings.bollingerLower,
|
||||
},
|
||||
새설정: localSettings,
|
||||
});
|
||||
updateSettings({
|
||||
...settings,
|
||||
...localSettings,
|
||||
});
|
||||
updateColors(localColors);
|
||||
console.log('✅ [볼린저밴드 설정] updateSettings 및 updateColors 호출 완료');
|
||||
}
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
if (multiChartToolbar) {
|
||||
const { localSettings: ls, localColors: lc } = bollingerDialogDefaultsFromStore();
|
||||
setLocalSettings(ls);
|
||||
setLocalColors(lc);
|
||||
setBandOpacity(extractOpacity(lc.bollingerBandBackgroundColor));
|
||||
} else {
|
||||
setLocalSettings({
|
||||
bollingerUpper: false,
|
||||
bollingerMiddle: false,
|
||||
bollingerLower: false,
|
||||
bollingerPeriod: 20,
|
||||
bollingerStdDev: 2.0,
|
||||
});
|
||||
setLocalColors({
|
||||
bollingerUpperColor: '#FF9800',
|
||||
bollingerMiddleColor: '#2196F3',
|
||||
bollingerLowerColor: '#4CAF50',
|
||||
bollingerUpperWidth: 1.5,
|
||||
bollingerMiddleWidth: 1.5,
|
||||
bollingerLowerWidth: 1.5,
|
||||
bollingerUpperLineStyle: 'solid' as const,
|
||||
bollingerMiddleLineStyle: 'solid' as const,
|
||||
bollingerLowerLineStyle: 'solid' as const,
|
||||
bollingerBandBackgroundColor: 'rgba(33, 150, 243, 0.15)',
|
||||
});
|
||||
setBandOpacity(0.15);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePeriodChange = (value: number) => {
|
||||
setLocalSettings({ ...localSettings, bollingerPeriod: value });
|
||||
};
|
||||
|
||||
const handleStdDevChange = (value: number) => {
|
||||
setLocalSettings({ ...localSettings, bollingerStdDev: value });
|
||||
};
|
||||
|
||||
// 색상 테이블 컴포넌트
|
||||
const ColorTable = () => {
|
||||
const configs: Array<{
|
||||
label: string;
|
||||
enableKey: 'bollingerUpper' | 'bollingerMiddle' | 'bollingerLower';
|
||||
colorKey: 'bollingerUpperColor' | 'bollingerMiddleColor' | 'bollingerLowerColor';
|
||||
widthKey: 'bollingerUpperWidth' | 'bollingerMiddleWidth' | 'bollingerLowerWidth';
|
||||
lineStyleKey: 'bollingerUpperLineStyle' | 'bollingerMiddleLineStyle' | 'bollingerLowerLineStyle';
|
||||
}> = [
|
||||
{
|
||||
label: '상한선',
|
||||
enableKey: 'bollingerUpper',
|
||||
colorKey: 'bollingerUpperColor',
|
||||
widthKey: 'bollingerUpperWidth',
|
||||
lineStyleKey: 'bollingerUpperLineStyle'
|
||||
},
|
||||
{
|
||||
label: '중간선',
|
||||
enableKey: 'bollingerMiddle',
|
||||
colorKey: 'bollingerMiddleColor',
|
||||
widthKey: 'bollingerMiddleWidth',
|
||||
lineStyleKey: 'bollingerMiddleLineStyle'
|
||||
},
|
||||
{
|
||||
label: '하한선',
|
||||
enableKey: 'bollingerLower',
|
||||
colorKey: 'bollingerLowerColor',
|
||||
widthKey: 'bollingerLowerWidth',
|
||||
lineStyleKey: 'bollingerLowerLineStyle'
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Box sx={{ mb: 0.5 }}>
|
||||
<Typography variant="caption" fontWeight="bold" color="text.secondary" sx={{ display: 'block', mb: 1, fontSize: '12px' }}>
|
||||
🎨 색상, 선 굵기, 선 유형
|
||||
</Typography>
|
||||
{/* 헤더 행 */}
|
||||
<Box sx={{
|
||||
p: 1,
|
||||
mb: 0.5,
|
||||
bgcolor: (theme) => theme.palette.mode === 'dark' ? '#616161' : '#9e9e9e',
|
||||
borderRadius: 1,
|
||||
}}>
|
||||
<Grid container spacing={1.5} alignItems="center">
|
||||
<Grid item xs={1}>
|
||||
<Typography variant="caption" fontWeight="bold" color="white">ON/OFF</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={1.5}>
|
||||
<Typography variant="caption" fontWeight="bold" color="white">항목</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={1.5}>
|
||||
<Typography variant="caption" fontWeight="bold" color="white">색상</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={2.5}>
|
||||
<Typography variant="caption" fontWeight="bold" color="white">굵기</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={2.5}>
|
||||
<Typography variant="caption" fontWeight="bold" color="white">선 유형</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Typography variant="caption" fontWeight="bold" color="white">미리보기</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
{/* 각 색상 설정 행 */}
|
||||
{configs.map((cfg) => {
|
||||
const colorValue = String(localColors[cfg.colorKey] || '#666666');
|
||||
const widthValue = Number(localColors[cfg.widthKey] ?? 1.5);
|
||||
const lineStyleValue = String(localColors[cfg.lineStyleKey] ?? 'solid');
|
||||
const enabledValue = Boolean(localSettings[cfg.enableKey]);
|
||||
|
||||
return (
|
||||
<Box key={cfg.colorKey} sx={{ p: 1, mb: 0.5, bgcolor: 'background.paper', borderRadius: 1, border: '1px solid', borderColor: 'divider' }}>
|
||||
<Grid container spacing={1} alignItems="center">
|
||||
{/* ON/OFF 토글 */}
|
||||
<Grid item xs={1} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Switch
|
||||
checked={enabledValue}
|
||||
onChange={(e) => setLocalSettings({ ...localSettings, [cfg.enableKey]: e.target.checked })}
|
||||
size="small"
|
||||
/>
|
||||
</Grid>
|
||||
{/* 항목 이름 */}
|
||||
<Grid item xs={1.5} sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Typography variant="body2" sx={{ fontSize: '12px', fontWeight: 'medium' }}>
|
||||
{cfg.label}
|
||||
</Typography>
|
||||
</Grid>
|
||||
{/* 색상 선택 */}
|
||||
<Grid item xs={1.5} sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<input
|
||||
type="color"
|
||||
value={colorValue}
|
||||
onChange={(e) => setLocalColors({ ...localColors, [cfg.colorKey]: e.target.value })}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '30px',
|
||||
border: '1px solid rgba(0, 0, 0, 0.23)',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
backgroundColor: colorValue,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
{/* 굵기 슬라이더 */}
|
||||
<Grid item xs={2.5} sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Slider
|
||||
value={widthValue}
|
||||
onChange={(_, value) => setLocalColors({ ...localColors, [cfg.widthKey]: value as number })}
|
||||
min={1}
|
||||
max={5}
|
||||
step={0.5}
|
||||
size="small"
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
<Typography variant="caption" sx={{ minWidth: 35, textAlign: 'right', fontSize: '11px' }}>
|
||||
{widthValue}px
|
||||
</Typography>
|
||||
</Grid>
|
||||
{/* 선 유형 */}
|
||||
<Grid item xs={2.5}>
|
||||
<FormControl size="small" fullWidth>
|
||||
<Select
|
||||
value={lineStyleValue}
|
||||
onChange={(e) => setLocalColors({ ...localColors, [cfg.lineStyleKey]: e.target.value as 'solid' | 'dashed' | 'dotted' })}
|
||||
sx={{ height: 30, fontSize: '12px' }}
|
||||
>
|
||||
<MenuItem value="solid" sx={{ fontSize: '12px' }}>실선</MenuItem>
|
||||
<MenuItem value="dashed" sx={{ fontSize: '12px' }}>파선</MenuItem>
|
||||
<MenuItem value="dotted" sx={{ fontSize: '12px' }}>점선</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
{/* 미리보기 */}
|
||||
<Grid item xs={3} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: 30 }}>
|
||||
<svg width="100%" height="100%" style={{ display: 'block' }}>
|
||||
<line
|
||||
x1="0"
|
||||
y1="50%"
|
||||
x2="100%"
|
||||
y2="50%"
|
||||
stroke={colorValue}
|
||||
strokeWidth={widthValue}
|
||||
strokeDasharray={lineStyleValue === 'dashed' ? '5,5' : lineStyleValue === 'dotted' ? '2,3' : '0'}
|
||||
/>
|
||||
</svg>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
PaperComponent={DraggablePaper}
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
PaperProps={{
|
||||
sx: {
|
||||
maxHeight: '90vh',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle sx={{
|
||||
cursor: 'move',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
bgcolor: '#1976d2',
|
||||
color: '#fff',
|
||||
py: 1.2,
|
||||
}}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 600, fontSize: '16px', color: '#fff' }}>
|
||||
볼린저 밴드 설정
|
||||
</Typography>
|
||||
<Tooltip title="초기화">
|
||||
<IconButton size="small" onClick={handleReset} sx={{ color: '#fff', '&:hover': { bgcolor: 'rgba(255,255,255,0.1)' } }}>
|
||||
<Refresh />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{ display: 'flex', alignItems: 'center', gap: 1 }}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onClose();
|
||||
}}
|
||||
sx={{
|
||||
color: '#fff',
|
||||
borderColor: '#fff',
|
||||
'&:hover': {
|
||||
bgcolor: 'rgba(255,255,255,0.1)',
|
||||
borderColor: '#fff'
|
||||
},
|
||||
minWidth: '70px',
|
||||
fontSize: '13px',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleSave();
|
||||
}}
|
||||
sx={{
|
||||
bgcolor: '#fff',
|
||||
color: '#1976d2',
|
||||
'&:hover': {
|
||||
bgcolor: 'rgba(255,255,255,0.9)'
|
||||
},
|
||||
minWidth: '70px',
|
||||
fontSize: '13px',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
variant="contained"
|
||||
size="small"
|
||||
>
|
||||
적용
|
||||
</Button>
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent sx={{ pt: 3 }}>
|
||||
<Alert severity="info" sx={{ mb: 3 }}>
|
||||
볼린저 밴드는 가격의 변동성을 측정하는 지표입니다. 상한선과 하한선은 표준편차를 이용하여 계산됩니다.
|
||||
</Alert>
|
||||
|
||||
{/* 기간 설정 */}
|
||||
<Paper sx={{ p: 2, mb: 2, border: '1px solid', borderColor: 'divider', bgcolor: 'action.hover' }}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 'bold', mb: 2 }}>
|
||||
파라미터 설정
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="caption" sx={{ mb: 0.5, display: 'block' }}>
|
||||
기간 (Period): {localSettings.bollingerPeriod}일
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Slider
|
||||
value={localSettings.bollingerPeriod}
|
||||
onChange={(_, value) => handlePeriodChange(value as number)}
|
||||
min={5}
|
||||
max={50}
|
||||
step={1}
|
||||
marks={[
|
||||
{ value: 10, label: '10' },
|
||||
{ value: 20, label: '20' },
|
||||
{ value: 30, label: '30' },
|
||||
{ value: 40, label: '40' },
|
||||
]}
|
||||
valueLabelDisplay="auto"
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
<TextField
|
||||
type="number"
|
||||
value={localSettings.bollingerPeriod}
|
||||
onChange={(e) => handlePeriodChange(Math.max(5, Math.min(50, Number(e.target.value))))}
|
||||
size="small"
|
||||
sx={{ width: 70 }}
|
||||
inputProps={{ min: 5, max: 50 }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Typography variant="caption" sx={{ mb: 0.5, display: 'block' }}>
|
||||
표준편차 승수 (StdDev): {localSettings.bollingerStdDev}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Slider
|
||||
value={localSettings.bollingerStdDev}
|
||||
onChange={(_, value) => handleStdDevChange(value as number)}
|
||||
min={1.0}
|
||||
max={3.0}
|
||||
step={0.1}
|
||||
marks={[
|
||||
{ value: 1.5, label: '1.5' },
|
||||
{ value: 2.0, label: '2.0' },
|
||||
{ value: 2.5, label: '2.5' },
|
||||
]}
|
||||
valueLabelDisplay="auto"
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
<TextField
|
||||
type="number"
|
||||
value={localSettings.bollingerStdDev}
|
||||
onChange={(e) => handleStdDevChange(Math.max(1.0, Math.min(3.0, Number(e.target.value))))}
|
||||
size="small"
|
||||
sx={{ width: 70 }}
|
||||
inputProps={{ min: 1.0, max: 3.0, step: 0.1 }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* 색상 및 선 설정 테이블 */}
|
||||
<ColorTable />
|
||||
|
||||
{/* 배경색 설정 */}
|
||||
<Paper sx={{ p: 2, mt: 2, border: '1px solid', borderColor: 'divider', bgcolor: 'action.hover' }}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 'bold', mb: 2 }}>
|
||||
배경색 설정
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Grid container spacing={2} alignItems="center">
|
||||
<Grid item xs={3}>
|
||||
<Typography variant="body2">밴드 배경색</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 1,
|
||||
border: '1px solid rgba(0, 0, 0, 0.23)',
|
||||
position: 'relative',
|
||||
cursor: 'pointer',
|
||||
backgroundColor: localColors.bollingerBandBackgroundColor,
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="color"
|
||||
value={extractRgbFromRgba(localColors.bollingerBandBackgroundColor)}
|
||||
onChange={(e) => {
|
||||
const rgbaColor = hexToRgba(e.target.value, bandOpacity);
|
||||
setLocalColors({ ...localColors, bollingerBandBackgroundColor: rgbaColor });
|
||||
}}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
opacity: 0,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Typography variant="body2" sx={{ fontFamily: 'monospace', fontSize: '12px', minWidth: 160 }}>
|
||||
{localColors.bollingerBandBackgroundColor}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||
투명도: {(bandOpacity * 100).toFixed(0)}%
|
||||
</Typography>
|
||||
<Slider
|
||||
value={bandOpacity}
|
||||
onChange={(_, value) => {
|
||||
const newOpacity = value as number;
|
||||
setBandOpacity(newOpacity);
|
||||
const baseColor = extractRgbFromRgba(localColors.bollingerBandBackgroundColor);
|
||||
const rgbaColor = hexToRgba(baseColor, newOpacity);
|
||||
setLocalColors({ ...localColors, bollingerBandBackgroundColor: rgbaColor });
|
||||
}}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.05}
|
||||
valueLabelDisplay="auto"
|
||||
valueLabelFormat={(value) => `${(value * 100).toFixed(0)}%`}
|
||||
sx={{ width: '100%' }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 1 }}>
|
||||
상한선과 하한선 사이 영역을 반투명하게 표시합니다
|
||||
</Typography>
|
||||
</Paper>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default BollingerSettingsDialog;
|
||||
Reference in New Issue
Block a user