차트 세로크로스헤어 교차점 on off
This commit is contained in:
@@ -122,6 +122,11 @@ public class GcAppSettings {
|
||||
@Builder.Default
|
||||
private Boolean chartCrosshairInfoVisible = true;
|
||||
|
||||
/** 세로 크로스헤어와 지표 선 교차점 점 표시 (기본 true) */
|
||||
@Column(name = "chart_crosshair_markers_visible", nullable = false)
|
||||
@Builder.Default
|
||||
private Boolean chartCrosshairMarkersVisible = true;
|
||||
|
||||
/** 차트 마우스오버 줌·이동 플로팅 툴바(− + ⌐↔ ‹ ›) 표시 (기본 true) */
|
||||
@Column(name = "chart_hover_toolbar_visible", nullable = false)
|
||||
@Builder.Default
|
||||
|
||||
@@ -100,6 +100,8 @@ public class AppSettingsService {
|
||||
Boolean.parseBoolean(d.get("chartLiveReceiveHighlight").toString()));
|
||||
if (d.containsKey("chartCrosshairInfoVisible")) s.setChartCrosshairInfoVisible(
|
||||
Boolean.parseBoolean(d.get("chartCrosshairInfoVisible").toString()));
|
||||
if (d.containsKey("chartCrosshairMarkersVisible")) s.setChartCrosshairMarkersVisible(
|
||||
Boolean.parseBoolean(d.get("chartCrosshairMarkersVisible").toString()));
|
||||
if (d.containsKey("chartHoverToolbarVisible")) s.setChartHoverToolbarVisible(
|
||||
Boolean.parseBoolean(d.get("chartHoverToolbarVisible").toString()));
|
||||
if (d.containsKey("chartLegendOptions")) s.setChartLegendOptionsJson(toJson(d.get("chartLegendOptions")));
|
||||
@@ -219,6 +221,7 @@ public class AppSettingsService {
|
||||
m.put("chartVolumeVisible", s.getChartVolumeVisible() != null ? s.getChartVolumeVisible() : true);
|
||||
m.put("chartLiveReceiveHighlight", s.getChartLiveReceiveHighlight() != null ? s.getChartLiveReceiveHighlight() : true);
|
||||
m.put("chartCrosshairInfoVisible", s.getChartCrosshairInfoVisible() != null ? s.getChartCrosshairInfoVisible() : true);
|
||||
m.put("chartCrosshairMarkersVisible", s.getChartCrosshairMarkersVisible() != null ? s.getChartCrosshairMarkersVisible() : true);
|
||||
m.put("chartHoverToolbarVisible", s.getChartHoverToolbarVisible() != null ? s.getChartHoverToolbarVisible() : true);
|
||||
m.put("chartLegendOptions", parseJson(s.getChartLegendOptionsJson()));
|
||||
m.put("chartPaneSeparator", parseJson(s.getChartPaneSeparatorJson()));
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE gc_app_settings
|
||||
ADD COLUMN chart_crosshair_markers_visible BOOLEAN NOT NULL DEFAULT TRUE
|
||||
COMMENT '세로 크로스헤어와 지표 선 교차점 점 표시';
|
||||
@@ -52,6 +52,8 @@ const NULL_SETTINGS_PAGE_PROPS: SettingsPageChartBindings = {
|
||||
onChartLegendOptionsChange: () => {},
|
||||
chartCrosshairInfoVisible: true,
|
||||
onChartCrosshairInfoVisible: () => {},
|
||||
chartCrosshairMarkersVisible: true,
|
||||
onChartCrosshairMarkersVisible: () => {},
|
||||
chartHoverToolbarVisible: true,
|
||||
onChartHoverToolbarVisible: () => {},
|
||||
chartPaneSeparator: undefined,
|
||||
|
||||
@@ -192,6 +192,8 @@ export interface SettingsPageChartBindings {
|
||||
onChartLegendOptionsChange: (patch: Partial<import('../types/chartLegend').ChartLegendVisibility>) => void;
|
||||
chartCrosshairInfoVisible: boolean;
|
||||
onChartCrosshairInfoVisible: (v: boolean) => void;
|
||||
chartCrosshairMarkersVisible: boolean;
|
||||
onChartCrosshairMarkersVisible: (v: boolean) => void;
|
||||
chartHoverToolbarVisible: boolean;
|
||||
onChartHoverToolbarVisible: (v: boolean) => void;
|
||||
chartPaneSeparator: import('../types/chartPaneSeparator').ChartPaneSeparatorOptions | undefined;
|
||||
@@ -610,6 +612,7 @@ export function useChartWorkspace({
|
||||
const chartLiveReceiveHighlight = appDefaults.chartLiveReceiveHighlight ?? true;
|
||||
const chartLegendOptions = resolveChartLegendOptions(appDefaults.chartLegendOptions);
|
||||
const chartCrosshairInfoVisible = appDefaults.chartCrosshairInfoVisible ?? true;
|
||||
const chartCrosshairMarkersVisible = appDefaults.chartCrosshairMarkersVisible ?? true;
|
||||
const chartHoverToolbarVisible = appDefaults.chartHoverToolbarVisible ?? true;
|
||||
const chartPaneSeparator = resolveChartPaneSeparatorOptions(appDefaults.chartPaneSeparator);
|
||||
const paperTradingEnabled = appDefaults.paperTradingEnabled ?? true;
|
||||
@@ -1010,6 +1013,12 @@ export function useChartWorkspace({
|
||||
});
|
||||
}, [appSettingsLoaded, chartCandleAreaPriceLabels, chartSeriesPriceLabels]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!appSettingsLoaded) return;
|
||||
managerRef.current?.setCrosshairMarkersVisible(chartCrosshairMarkersVisible);
|
||||
slotRefs.current.forEach(slot => slot?.setCrosshairMarkersVisible(chartCrosshairMarkersVisible));
|
||||
}, [appSettingsLoaded, chartCrosshairMarkersVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!appSettingsLoaded) return;
|
||||
const wrapper = document.querySelector('.chart-wrapper') as HTMLElement | null;
|
||||
@@ -1903,6 +1912,12 @@ export function useChartWorkspace({
|
||||
},
|
||||
chartCrosshairInfoVisible,
|
||||
onChartCrosshairInfoVisible: v => saveAppDef({ chartCrosshairInfoVisible: v }),
|
||||
chartCrosshairMarkersVisible,
|
||||
onChartCrosshairMarkersVisible: v => {
|
||||
saveAppDef({ chartCrosshairMarkersVisible: v });
|
||||
managerRef.current?.setCrosshairMarkersVisible(v);
|
||||
slotRefs.current.forEach(slot => slot?.setCrosshairMarkersVisible(v));
|
||||
},
|
||||
chartHoverToolbarVisible,
|
||||
onChartHoverToolbarVisible: v => saveAppDef({ chartHoverToolbarVisible: v }),
|
||||
chartPaneSeparator,
|
||||
@@ -1929,6 +1944,7 @@ export function useChartWorkspace({
|
||||
handleAddIndicator, handleRemoveByType, handleIndicatorListOrderChange,
|
||||
chartCandleAreaPriceLabels, chartSeriesPriceLabels, chartVolumeVisible,
|
||||
chartLiveReceiveHighlight, chartLegendOptions, chartCrosshairInfoVisible,
|
||||
chartCrosshairMarkersVisible,
|
||||
chartHoverToolbarVisible, chartPaneSeparator, saveAppDef, clearLiveMarkers,
|
||||
refreshPaperAccount, handleTimezoneChange, handleChartTimeFormatChange,
|
||||
handleTradeAlertTimeFormatChange, displayTimezone, chartTimeFormat, tradeAlertTimeFormat,
|
||||
|
||||
@@ -137,6 +137,7 @@ export interface ChartSlotHandle {
|
||||
setSeriesPriceLabelsEnabled: (enabled: boolean) => void;
|
||||
setCandleAreaPriceLabelsEnabled: (enabled: boolean) => void;
|
||||
setIndicatorAreaPriceLabelsEnabled: (enabled: boolean) => void;
|
||||
setCrosshairMarkersVisible: (enabled: boolean) => void;
|
||||
/** 거래량 pane on/off */
|
||||
setVolumeVisible: (visible: boolean) => void;
|
||||
/** 9·20일 신고가·신저가 표시 */
|
||||
@@ -382,6 +383,9 @@ const ChartSlot = forwardRef<ChartSlotHandle, ChartSlotProps>(function ChartSlot
|
||||
setIndicatorAreaPriceLabelsEnabled: (enabled: boolean) => {
|
||||
managerRef.current?.setIndicatorAreaPriceLabelsEnabled(enabled);
|
||||
},
|
||||
setCrosshairMarkersVisible: (enabled: boolean) => {
|
||||
managerRef.current?.setCrosshairMarkersVisible(enabled);
|
||||
},
|
||||
setVolumeVisible: (visible: boolean) => {
|
||||
managerRef.current?.setVolumeVisible(visible);
|
||||
},
|
||||
|
||||
@@ -129,6 +129,8 @@ interface SettingsPageProps {
|
||||
onChartLegendOptionsChange?: (patch: Partial<ChartLegendVisibility>) => void;
|
||||
chartCrosshairInfoVisible?: boolean;
|
||||
onChartCrosshairInfoVisible?: (v: boolean) => void;
|
||||
chartCrosshairMarkersVisible?: boolean;
|
||||
onChartCrosshairMarkersVisible?: (v: boolean) => void;
|
||||
chartHoverToolbarVisible?: boolean;
|
||||
onChartHoverToolbarVisible?: (v: boolean) => void;
|
||||
chartPaneSeparator?: ChartPaneSeparatorOptions;
|
||||
@@ -1010,6 +1012,8 @@ interface ChartPanelProps {
|
||||
onChartLegendOptionsChange?: (patch: Partial<ChartLegendVisibility>) => void;
|
||||
chartCrosshairInfoVisible?: boolean;
|
||||
onChartCrosshairInfoVisible?: (v: boolean) => void;
|
||||
chartCrosshairMarkersVisible?: boolean;
|
||||
onChartCrosshairMarkersVisible?: (v: boolean) => void;
|
||||
chartHoverToolbarVisible?: boolean;
|
||||
onChartHoverToolbarVisible?: (v: boolean) => void;
|
||||
chartPaneSeparator?: ChartPaneSeparatorOptions;
|
||||
@@ -1036,6 +1040,8 @@ const ChartPanel: React.FC<ChartPanelProps> = ({
|
||||
onChartLegendOptionsChange,
|
||||
chartCrosshairInfoVisible = true,
|
||||
onChartCrosshairInfoVisible,
|
||||
chartCrosshairMarkersVisible = true,
|
||||
onChartCrosshairMarkersVisible,
|
||||
chartHoverToolbarVisible = true,
|
||||
onChartHoverToolbarVisible,
|
||||
chartPaneSeparator,
|
||||
@@ -1248,6 +1254,19 @@ const ChartPanel: React.FC<ChartPanelProps> = ({
|
||||
<span className="stg-toggle-slider" />
|
||||
</label>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
label="교차점 점 표시"
|
||||
desc="켜면 세로 크로스헤어가 캔들·보조지표 선과 만나는 위치에 색 점이 표시됩니다. 끄면 크로스헤어 선만 표시됩니다."
|
||||
>
|
||||
<label className="stg-toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={chartCrosshairMarkersVisible}
|
||||
onChange={e => onChartCrosshairMarkersVisible?.(e.target.checked)}
|
||||
/>
|
||||
<span className="stg-toggle-slider" />
|
||||
</label>
|
||||
</SettingRow>
|
||||
</SettingSection>
|
||||
)}
|
||||
|
||||
@@ -1955,6 +1974,8 @@ const SettingsPage: React.FC<SettingsPageProps> = ({
|
||||
onChartLegendOptionsChange,
|
||||
chartCrosshairInfoVisible = true,
|
||||
onChartCrosshairInfoVisible,
|
||||
chartCrosshairMarkersVisible = true,
|
||||
onChartCrosshairMarkersVisible,
|
||||
chartHoverToolbarVisible = true,
|
||||
onChartHoverToolbarVisible,
|
||||
chartPaneSeparator,
|
||||
@@ -2077,6 +2098,8 @@ const SettingsPage: React.FC<SettingsPageProps> = ({
|
||||
onChartLegendOptionsChange={onChartLegendOptionsChange}
|
||||
chartCrosshairInfoVisible={chartCrosshairInfoVisible}
|
||||
onChartCrosshairInfoVisible={onChartCrosshairInfoVisible}
|
||||
chartCrosshairMarkersVisible={chartCrosshairMarkersVisible}
|
||||
onChartCrosshairMarkersVisible={onChartCrosshairMarkersVisible}
|
||||
chartHoverToolbarVisible={chartHoverToolbarVisible}
|
||||
onChartHoverToolbarVisible={onChartHoverToolbarVisible}
|
||||
chartPaneSeparator={chartPaneSeparator}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { formatUpbitKrwPrice } from '../utils/safeFormat';
|
||||
import { useChartTimeFormat } from '../utils/chartTimeFormat';
|
||||
import { setIndicatorChartContext } from '../utils/indicatorRegistry';
|
||||
import { DISPLAY_COUNT } from '../hooks/useUpbitData';
|
||||
import { useAppSettings, resolveAppDefaults } from '../hooks/useAppSettings';
|
||||
|
||||
/** Upbit 실시간 — 초기 히스토리가 이 개수 미만이면 setData 하지 않음 (종목 전환 직후 sparse 봉 → 캔들 폭 비정상) */
|
||||
const MIN_BARS_FOR_FULL_RELOAD = Math.min(50, Math.floor(DISPLAY_COUNT / 4));
|
||||
@@ -165,6 +166,8 @@ interface TradingChartProps {
|
||||
showCandleOverlayControls?: boolean;
|
||||
/** 크로스헤어 이동 시 OHLC·보조지표 수치 표시 (기본 true) */
|
||||
crosshairInfoVisible?: boolean;
|
||||
/** 세로 크로스헤어와 선 그래프 교차점 점 표시 (미지정 시 전역 차트 설정) */
|
||||
crosshairMarkersVisible?: boolean;
|
||||
/** 외부 캔들 데이터 fetch 중 (실시간 차트 초기 로딩) */
|
||||
dataLoading?: boolean;
|
||||
/** chartPaintReady 변경 알림 (워크스페이스 전체 로딩 오버레이용) */
|
||||
@@ -237,6 +240,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
showChartRightToolbar,
|
||||
showCandleOverlayControls = false,
|
||||
crosshairInfoVisible = true,
|
||||
crosshairMarkersVisible,
|
||||
dataLoading = false,
|
||||
onChartPaintReadyChange,
|
||||
defaultCandleOnly = false,
|
||||
@@ -533,6 +537,10 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
mgr.setVolumeVisible(volumeVisible, wrapperH > 0 ? wrapperH : undefined);
|
||||
}, [volumeVisible]);
|
||||
|
||||
const { settings } = useAppSettings();
|
||||
const appDefaults = resolveAppDefaults(settings);
|
||||
const resolvedCrosshairMarkers = crosshairMarkersVisible ?? appDefaults.chartCrosshairMarkersVisible ?? true;
|
||||
|
||||
const resolvedCandlePriceLabels = candleAreaPriceLabelsEnabled ?? seriesPriceLabelsEnabled ?? true;
|
||||
const resolvedIndicatorPriceLabels = indicatorAreaPriceLabelsEnabled ?? seriesPriceLabelsEnabled ?? true;
|
||||
|
||||
@@ -544,6 +552,10 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
managerRef.current?.setIndicatorAreaPriceLabelsEnabled(resolvedIndicatorPriceLabels);
|
||||
}, [resolvedIndicatorPriceLabels]);
|
||||
|
||||
useEffect(() => {
|
||||
managerRef.current?.setCrosshairMarkersVisible(resolvedCrosshairMarkers);
|
||||
}, [resolvedCrosshairMarkers]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!paneSeparatorOptions) return;
|
||||
managerRef.current?.setPaneSeparatorOptions(paneSeparatorOptions);
|
||||
@@ -1025,6 +1037,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
||||
}
|
||||
mgr.setCandleAreaPriceLabelsEnabled(resolvedCandlePriceLabels);
|
||||
mgr.setIndicatorAreaPriceLabelsEnabled(resolvedIndicatorPriceLabels);
|
||||
mgr.setCrosshairMarkersVisible(resolvedCrosshairMarkers);
|
||||
if (paneSeparatorOptions) mgr.setPaneSeparatorOptions(paneSeparatorOptions);
|
||||
if (auxIndicatorOnlyRef.current) mgr.applyAuxIndicatorOnlyLayout(true);
|
||||
onManagerReady(mgr);
|
||||
|
||||
@@ -8,6 +8,8 @@ interface Props {
|
||||
indicatorAreaEnabled: boolean;
|
||||
onCandleAreaChange: (enabled: boolean) => void;
|
||||
onIndicatorAreaChange: (enabled: boolean) => void;
|
||||
crosshairMarkersEnabled?: boolean;
|
||||
onCrosshairMarkersChange?: (enabled: boolean) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -16,8 +18,11 @@ const ChartPriceAxisLabelSettings: React.FC<Props> = ({
|
||||
indicatorAreaEnabled,
|
||||
onCandleAreaChange,
|
||||
onIndicatorAreaChange,
|
||||
crosshairMarkersEnabled = true,
|
||||
onCrosshairMarkersChange,
|
||||
className = '',
|
||||
}) => (
|
||||
<>
|
||||
<section className={`seval-price-label-settings${className ? ` ${className}` : ''}`}>
|
||||
<h3 className="seval-price-label-settings-title">지표 가격축 라벨·설명</h3>
|
||||
<div className="seval-price-label-settings-body">
|
||||
@@ -68,6 +73,30 @@ const ChartPriceAxisLabelSettings: React.FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{onCrosshairMarkersChange && (
|
||||
<section className={`seval-price-label-settings seval-crosshair-marker-settings${className ? ` ${className}` : ''}`}>
|
||||
<h3 className="seval-price-label-settings-title">크로스헤어 교차점</h3>
|
||||
<div className="seval-price-label-settings-body">
|
||||
<div className="seval-price-label-row">
|
||||
<div className="seval-price-label-row-text">
|
||||
<span className="seval-price-label-row-title">교차점 점 표시</span>
|
||||
<span className="seval-price-label-row-desc">
|
||||
켜면 세로 크로스헤어가 캔들·보조지표 선과 만나는 위치에 색 점이 표시됩니다.
|
||||
</span>
|
||||
</div>
|
||||
<label className="ism-toggle seval-price-label-toggle" title="크로스헤어 교차점 점 표시">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={crosshairMarkersEnabled}
|
||||
onChange={e => onCrosshairMarkersChange(e.target.checked)}
|
||||
/>
|
||||
<span className="ism-toggle-slider" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
export default ChartPriceAxisLabelSettings;
|
||||
|
||||
@@ -99,6 +99,7 @@ const StrategyEvaluationChartSettingsTab: React.FC<Props> = ({
|
||||
const appDefaults = useMemo(() => resolveAppDefaults(settings), [settings]);
|
||||
const candlePriceLabels = appDefaults.chartCandleAreaPriceLabels !== false;
|
||||
const indicatorPriceLabels = appDefaults.chartSeriesPriceLabels !== false;
|
||||
const crosshairMarkers = appDefaults.chartCrosshairMarkersVisible !== false;
|
||||
|
||||
const reloadFromDb = useCallback(() => {
|
||||
const next: Record<string, IndicatorConfig> = {};
|
||||
@@ -221,6 +222,8 @@ const StrategyEvaluationChartSettingsTab: React.FC<Props> = ({
|
||||
indicatorAreaEnabled={indicatorPriceLabels}
|
||||
onCandleAreaChange={v => saveAppSettings({ chartCandleAreaPriceLabels: v })}
|
||||
onIndicatorAreaChange={v => saveAppSettings({ chartSeriesPriceLabels: v })}
|
||||
crosshairMarkersEnabled={crosshairMarkers}
|
||||
onCrosshairMarkersChange={v => saveAppSettings({ chartCrosshairMarkersVisible: v })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -177,6 +177,7 @@ export function resolveAppDefaults(s: AppSettingsDto) {
|
||||
chartVolumeVisible: s.chartVolumeVisible ?? true,
|
||||
chartLiveReceiveHighlight: s.chartLiveReceiveHighlight ?? true,
|
||||
chartCrosshairInfoVisible: s.chartCrosshairInfoVisible ?? true,
|
||||
chartCrosshairMarkersVisible: s.chartCrosshairMarkersVisible ?? true,
|
||||
chartHoverToolbarVisible: s.chartHoverToolbarVisible ?? true,
|
||||
chartLegendOptions: resolveChartLegendOptions(
|
||||
s.chartLegendOptions as Partial<ChartLegendVisibility> | null | undefined,
|
||||
|
||||
@@ -347,6 +347,8 @@ export class ChartManager {
|
||||
private _candleAreaPriceLabelsEnabled = true;
|
||||
/** 하단 보조지표 pane(2+) — 우측 가격축 라벨·설명 */
|
||||
private _indicatorAreaPriceLabelsEnabled = true;
|
||||
/** 세로 크로스헤어와 선 그래프 교차점 점 표시 */
|
||||
private _crosshairMarkersVisible = true;
|
||||
|
||||
/** 차트 하단 거래량 pane 표시 */
|
||||
private _volumeVisible = true;
|
||||
@@ -596,15 +598,23 @@ export class ChartManager {
|
||||
borderUpColor: t.upColor, borderDownColor: t.downColor,
|
||||
wickUpColor: t.upColor, wickDownColor: t.downColor,
|
||||
priceFormat: MAIN_PRICE_FORMAT,
|
||||
...this._crosshairMarkerSeriesOptions(),
|
||||
});
|
||||
case 'bar':
|
||||
return this.chart.addSeries(BarSeries, { upColor: t.upColor, downColor: t.downColor, priceFormat: MAIN_PRICE_FORMAT });
|
||||
return this.chart.addSeries(BarSeries, {
|
||||
upColor: t.upColor, downColor: t.downColor, priceFormat: MAIN_PRICE_FORMAT,
|
||||
...this._crosshairMarkerSeriesOptions(),
|
||||
});
|
||||
case 'line':
|
||||
return this.chart.addSeries(LineSeries, { color: '#2962FF', lineWidth: 2, crosshairMarkerVisible: true, priceFormat: MAIN_PRICE_FORMAT });
|
||||
return this.chart.addSeries(LineSeries, {
|
||||
color: '#2962FF', lineWidth: 2, priceFormat: MAIN_PRICE_FORMAT,
|
||||
...this._crosshairMarkerSeriesOptions(),
|
||||
});
|
||||
case 'area':
|
||||
return this.chart.addSeries(AreaSeries, {
|
||||
lineColor: '#2962FF', topColor: '#2962FF44', bottomColor: '#2962FF00', lineWidth: 2,
|
||||
priceFormat: MAIN_PRICE_FORMAT,
|
||||
...this._crosshairMarkerSeriesOptions(),
|
||||
});
|
||||
case 'baseline':
|
||||
return this.chart.addSeries(BaselineSeries, {
|
||||
@@ -612,6 +622,7 @@ export class ChartManager {
|
||||
topLineColor: '#26a69a', topFillColor1: '#26a69a44', topFillColor2: '#26a69a00',
|
||||
bottomLineColor: '#ef5350', bottomFillColor1: '#ef535000', bottomFillColor2: '#ef535044',
|
||||
priceFormat: MAIN_PRICE_FORMAT,
|
||||
...this._crosshairMarkerSeriesOptions(),
|
||||
});
|
||||
default:
|
||||
return this.chart.addSeries(CandlestickSeries, {
|
||||
@@ -619,6 +630,7 @@ export class ChartManager {
|
||||
borderUpColor: t.upColor, borderDownColor: t.downColor,
|
||||
wickUpColor: t.upColor, wickDownColor: t.downColor,
|
||||
priceFormat: MAIN_PRICE_FORMAT,
|
||||
...this._crosshairMarkerSeriesOptions(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -979,8 +991,7 @@ export class ChartManager {
|
||||
lineStyle: plotSeriesLineStyle(plotDef.lineStyle),
|
||||
priceLineVisible: false,
|
||||
lastValueVisible: showPriceLabel,
|
||||
crosshairMarkerVisible: true,
|
||||
crosshairMarkerRadius: plotId === 'plot0' ? 4 : 3,
|
||||
...this._crosshairMarkerSeriesOptions(plotId === 'plot0' ? 4 : 3),
|
||||
title: showSeriesTitle ? this.seriesTitleForPlot(enriched, plotDef) : '',
|
||||
visible: isPlotVisible,
|
||||
priceScaleId: obvScaleId,
|
||||
@@ -1046,7 +1057,7 @@ export class ChartManager {
|
||||
lineWidth: Math.max(1, plotDef.lineWidth ?? regPlot?.lineWidth ?? 1) as 1 | 2 | 3 | 4,
|
||||
lineStyle: plotSeriesLineStyle(plotDef.lineStyle),
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: true,
|
||||
...this._crosshairMarkerSeriesOptions(),
|
||||
lastValueVisible: showPriceLabel,
|
||||
title: showSeriesTitle ? this.seriesTitleForPlot(config, plotDef) : '',
|
||||
visible: isPlotVisible,
|
||||
@@ -1100,6 +1111,7 @@ export class ChartManager {
|
||||
lastValueVisible: showPriceLabel,
|
||||
title: showSeriesTitle ? this.seriesTitleForPlot(config, plotDef) : '',
|
||||
visible: isPlotVisible,
|
||||
...this._crosshairMarkerSeriesOptions(),
|
||||
...(indicatorPriceFormat ? { priceFormat: indicatorPriceFormat } : {}),
|
||||
}, pane);
|
||||
series.setData(filtered.map(p => ({ time: p.time as Time, value: p.value })));
|
||||
@@ -1277,7 +1289,7 @@ export class ChartManager {
|
||||
lineWidth: Math.max(1, plotDef.lineWidth ?? regPlot?.lineWidth ?? 1) as 1 | 2 | 3 | 4,
|
||||
lineStyle: plotSeriesLineStyle(plotDef.lineStyle),
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: true,
|
||||
...this._crosshairMarkerSeriesOptions(),
|
||||
lastValueVisible: showPriceLabel,
|
||||
title: showSeriesTitle ? this.seriesTitleForPlot(config, plotDef) : '',
|
||||
visible: isPlotVisible,
|
||||
@@ -2089,6 +2101,7 @@ export class ChartManager {
|
||||
lastValueVisible: showPriceLabel && lineVisible,
|
||||
title: showPriceLabel && lineVisible ? info.title : '',
|
||||
visible: lineVisible,
|
||||
...this._crosshairMarkerSeriesOptions(),
|
||||
}, pane);
|
||||
series.setData(seriesData);
|
||||
seriesList.push(series);
|
||||
@@ -3544,6 +3557,57 @@ export class ChartManager {
|
||||
return this._indicatorAreaPriceLabelsEnabled;
|
||||
}
|
||||
|
||||
setCrosshairMarkersVisible(enabled: boolean): void {
|
||||
if (this._crosshairMarkersVisible === enabled) return;
|
||||
this._crosshairMarkersVisible = enabled;
|
||||
this._refreshCrosshairMarkerStyles();
|
||||
}
|
||||
|
||||
getCrosshairMarkersVisible(): boolean {
|
||||
return this._crosshairMarkersVisible;
|
||||
}
|
||||
|
||||
private _crosshairMarkerSeriesOptions(radius?: number): {
|
||||
crosshairMarkerVisible: boolean;
|
||||
crosshairMarkerRadius?: number;
|
||||
} {
|
||||
return {
|
||||
crosshairMarkerVisible: this._crosshairMarkersVisible,
|
||||
...(radius != null ? { crosshairMarkerRadius: radius } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
private _refreshCrosshairMarkerStyles(): void {
|
||||
const visible = this._crosshairMarkersVisible;
|
||||
if (this.mainSeries) {
|
||||
try {
|
||||
this.mainSeries.applyOptions({ crosshairMarkerVisible: visible } as Parameters<ISeriesApi<SeriesType>['applyOptions']>[0]);
|
||||
} catch { /* ok */ }
|
||||
}
|
||||
for (const s of this.compareSeriesMap.values()) {
|
||||
try {
|
||||
s.applyOptions({ crosshairMarkerVisible: visible } as Parameters<ISeriesApi<SeriesType>['applyOptions']>[0]);
|
||||
} catch { /* ok */ }
|
||||
}
|
||||
for (const entry of this.indicators.values()) {
|
||||
entry.seriesList.forEach((series, i) => {
|
||||
if (entry.seriesMeta[i]?.isHistogram) return;
|
||||
const pid = entry.seriesMeta[i]?.plotId;
|
||||
const radius = entry.type === 'OBV' && pid === 'plot0'
|
||||
? 4
|
||||
: entry.type === 'OBV' && pid === 'plot1'
|
||||
? 3
|
||||
: undefined;
|
||||
try {
|
||||
series.applyOptions({
|
||||
crosshairMarkerVisible: visible,
|
||||
...(radius != null ? { crosshairMarkerRadius: radius } : {}),
|
||||
} as Parameters<ISeriesApi<SeriesType>['applyOptions']>[0]);
|
||||
} catch { /* ok */ }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _refreshPriceLabelStyles(): void {
|
||||
for (const entry of this.indicators.values()) {
|
||||
this.applyIndicatorStyle(entry.config);
|
||||
@@ -3649,7 +3713,7 @@ export class ChartManager {
|
||||
: Math.max(1, plot.lineWidth ?? regPlot?.lineWidth ?? 1)) as 1 | 2 | 3 | 4)
|
||||
: Math.max(1, plot.lineWidth ?? regPlot?.lineWidth ?? 1);
|
||||
opts['lineStyle'] = plotSeriesLineStyle(plot.lineStyle);
|
||||
opts['crosshairMarkerVisible'] = true;
|
||||
opts['crosshairMarkerVisible'] = this._crosshairMarkersVisible;
|
||||
let plotAllows = true;
|
||||
if (entry.type === 'IchimokuCloud') {
|
||||
plotAllows = entry.seriesMeta[i]?.plotId !== 'plot2';
|
||||
@@ -4980,7 +5044,10 @@ export class ChartManager {
|
||||
// ─── Compare overlay ──────────────────────────────────────────────────────
|
||||
addCompareSeries(symbol: string, bars: OHLCVBar[], color: string): void {
|
||||
if (this.compareSeriesMap.has(symbol)) return;
|
||||
const s = this.chart.addSeries(LineSeries, { color, lineWidth: 2, priceLineVisible: false });
|
||||
const s = this.chart.addSeries(LineSeries, {
|
||||
color, lineWidth: 2, priceLineVisible: false,
|
||||
...this._crosshairMarkerSeriesOptions(),
|
||||
});
|
||||
const base = bars[0]?.close ?? 1;
|
||||
s.setData(bars.map(b => ({ time: b.time as Time, value: (b.close / base - 1) * 100 })));
|
||||
this.compareSeriesMap.set(symbol, s);
|
||||
|
||||
@@ -526,6 +526,8 @@ export interface AppSettingsDto {
|
||||
chartLiveReceiveHighlight?: boolean;
|
||||
/** 크로스헤어 이동 시 OHLC·보조지표 수치 표시 (기본 true) */
|
||||
chartCrosshairInfoVisible?: boolean;
|
||||
/** 세로 크로스헤어와 지표 선 교차점 점 표시 (기본 true) */
|
||||
chartCrosshairMarkersVisible?: boolean;
|
||||
/** 차트 마우스오버 줌·이동 플로팅 툴바 표시 (기본 true) */
|
||||
chartHoverToolbarVisible?: boolean;
|
||||
/** 차트 상단 범례(tv-legend) 항목별 표시 옵션 */
|
||||
|
||||
Reference in New Issue
Block a user