fix: implement touch support for mobile and fix timeframe-based data calculation bugs

This commit is contained in:
DiTus
2026-03-21 13:58:13 +01:00
parent 338b1ee895
commit b2a4a6963d

View File

@ -29,9 +29,25 @@ export class DrawingManager {
init() { init() {
const container = this.container; const container = this.container;
// Mouse Events
container.addEventListener('mousedown', (e) => this.handleMouseDown(e)); container.addEventListener('mousedown', (e) => this.handleMouseDown(e));
window.addEventListener('mousemove', (e) => this.handleMouseMove(e)); window.addEventListener('mousemove', (e) => this.handleMouseMove(e));
window.addEventListener('mouseup', (e) => this.handleMouseUp(e)); window.addEventListener('mouseup', (e) => this.handleMouseUp(e));
// Touch Events
container.addEventListener('touchstart', (e) => {
if (this.activeTool || this.selectedDrawing) e.preventDefault();
this.handleMouseDown(this.touchToMouseEvent(e));
}, { passive: false });
window.addEventListener('touchmove', (e) => {
if (this.activeTool || (this.selectedDrawing && this.isMouseDown)) e.preventDefault();
this.handleMouseMove(this.touchToMouseEvent(e));
}, { passive: false });
window.addEventListener('touchend', (e) => {
this.handleMouseUp();
});
window.addEventListener('keydown', (e) => { window.addEventListener('keydown', (e) => {
if ((e.key === 'Delete' || e.key === 'Backspace') && this.selectedDrawing) { if ((e.key === 'Delete' || e.key === 'Backspace') && this.selectedDrawing) {
@ -114,6 +130,16 @@ export class DrawingManager {
return views; return views;
} }
touchToMouseEvent(e) {
const touch = e.touches[0] || e.changedTouches[0];
return {
clientX: touch.clientX,
clientY: touch.clientY,
shiftKey: e.shiftKey,
preventDefault: () => e.preventDefault()
};
}
updateChartInteractions() { updateChartInteractions() {
const isInteracting = this.activeTool !== null || this.currentDrawing !== null || (this.selectedDrawing !== null && this.isMouseDown); const isInteracting = this.activeTool !== null || this.currentDrawing !== null || (this.selectedDrawing !== null && this.isMouseDown);
@ -377,6 +403,32 @@ export class DrawingManager {
if (this.requestUpdate) this.requestUpdate(); if (this.requestUpdate) this.requestUpdate();
} }
snapToNearestTime(time) {
if (time === null) return null;
const candleData = this.dashboard.allData.get(this.dashboard.currentInterval) || [];
if (candleData.length === 0) return time;
// Find nearest timestamp in current data
// Since data is sorted, we can be efficient, but for now a simple find is safest
let nearest = candleData[0].time;
let minDiff = Math.abs(time - nearest);
// Optimization: check if it's already a match
if (candleData.find(c => c.time === time)) return time;
for (let i = 1; i < candleData.length; i++) {
const diff = Math.abs(time - candleData[i].time);
if (diff < minDiff) {
minDiff = diff;
nearest = candleData[i].time;
} else if (diff > minDiff) {
// Since it's sorted, once diff starts increasing, we've found the closest
break;
}
}
return nearest;
}
render(target) { render(target) {
target.useMediaCoordinateSpace((scope) => { target.useMediaCoordinateSpace((scope) => {
const ctx = scope.context; const ctx = scope.context;
@ -384,18 +436,18 @@ export class DrawingManager {
const series = this.series; const series = this.series;
const allDrawings = [...this.drawings]; const allDrawings = [...this.drawings];
if (this.currentDrawing) allDrawings.push(this.currentDrawing); if (this.currentDrawing) allDrawings.push(this.currentDrawing);
allDrawings.forEach(d => { allDrawings.forEach(d => {
const isSelected = d === this.selectedDrawing; const isSelected = d === this.selectedDrawing;
ctx.save(); ctx.save();
ctx.strokeStyle = d.color; ctx.strokeStyle = d.color;
ctx.lineWidth = isSelected ? 3 : 2; ctx.lineWidth = isSelected ? 3 : 2;
if (isSelected) { ctx.shadowBlur = 5; ctx.shadowColor = d.color; } if (isSelected) { ctx.shadowBlur = 5; ctx.shadowColor = d.color; }
if (d.type === 'trend_line' || d.type === 'ray') { if (d.type === 'trend_line' || d.type === 'ray') {
const x1 = chart.timeScale().timeToCoordinate(d.p1.time); const x1 = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.p1.time));
const y1 = series.priceToCoordinate(d.p1.price); const y1 = series.priceToCoordinate(d.p1.price);
const x2 = chart.timeScale().timeToCoordinate(d.p2.time); const x2 = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.p2.time));
const y2 = series.priceToCoordinate(d.p2.price); const y2 = series.priceToCoordinate(d.p2.price);
if (x1 !== null && y1 !== null && x2 !== null && y2 !== null) { if (x1 !== null && y1 !== null && x2 !== null && y2 !== null) {
ctx.beginPath(); ctx.beginPath();
@ -414,9 +466,9 @@ export class DrawingManager {
} }
} }
} else if (d.type === 'rectangle') { } else if (d.type === 'rectangle') {
const x1 = chart.timeScale().timeToCoordinate(d.p1.time); const x1 = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.p1.time));
const y1 = series.priceToCoordinate(d.p1.price); const y1 = series.priceToCoordinate(d.p1.price);
const x2 = chart.timeScale().timeToCoordinate(d.p2.time); const x2 = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.p2.time));
const y2 = series.priceToCoordinate(d.p2.price); const y2 = series.priceToCoordinate(d.p2.price);
if (x1 !== null && y1 !== null && x2 !== null && y2 !== null) { if (x1 !== null && y1 !== null && x2 !== null && y2 !== null) {
ctx.fillStyle = d.color + '22'; ctx.fillStyle = d.color + '22';
@ -431,17 +483,23 @@ export class DrawingManager {
} else if (d.type === 'horizontal_line') { } else if (d.type === 'horizontal_line') {
const y = series.priceToCoordinate(d.price); const y = series.priceToCoordinate(d.price);
if (y !== null) { if (y !== null) {
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(scope.mediaSize.width, y); ctx.stroke(); ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(scope.mediaSize.width, y);
ctx.stroke();
} }
} else if (d.type === 'vertical_line') { } else if (d.type === 'vertical_line') {
const x = chart.timeScale().timeToCoordinate(d.time); const x = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.time));
if (x !== null) { if (x !== null) {
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, scope.mediaSize.height); ctx.stroke(); ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, scope.mediaSize.height);
ctx.stroke();
} }
} else if (d.type === 'fib_retracement') { } else if (d.type === 'fib_retracement') {
const x1 = chart.timeScale().timeToCoordinate(d.p1.time); const x1 = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.p1.time));
const y1 = series.priceToCoordinate(d.p1.price); const y1 = series.priceToCoordinate(d.p1.price);
const x2 = chart.timeScale().timeToCoordinate(d.p2.time); const x2 = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.p2.time));
const y2 = series.priceToCoordinate(d.p2.price); const y2 = series.priceToCoordinate(d.p2.price);
if (x1 !== null && y1 !== null && x2 !== null && y2 !== null) { if (x1 !== null && y1 !== null && x2 !== null && y2 !== null) {
const levels = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1]; const levels = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
@ -465,7 +523,7 @@ export class DrawingManager {
} }
} }
} else if (d.type === 'arrow') { } else if (d.type === 'arrow') {
const x = chart.timeScale().timeToCoordinate(d.time); const x = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.time));
const y = series.priceToCoordinate(d.price); const y = series.priceToCoordinate(d.price);
if (x !== null && y !== null) { if (x !== null && y !== null) {
ctx.fillStyle = d.color; ctx.beginPath(); ctx.fillStyle = d.color; ctx.beginPath();
@ -483,7 +541,7 @@ export class DrawingManager {
if (isSelected) { ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 1; ctx.stroke(); } if (isSelected) { ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 1; ctx.stroke(); }
} }
} else if (d.type === 'text') { } else if (d.type === 'text') {
const x = chart.timeScale().timeToCoordinate(d.time); const x = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.time));
const y = series.priceToCoordinate(d.price); const y = series.priceToCoordinate(d.price);
if (x !== null && y !== null) { if (x !== null && y !== null) {
ctx.fillStyle = d.color; ctx.fillStyle = d.color;
@ -496,12 +554,13 @@ export class DrawingManager {
} }
} }
} else if (d.type === 'measure') { } else if (d.type === 'measure') {
const x1 = chart.timeScale().timeToCoordinate(d.p1.time); const x1 = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.p1.time));
const y1 = series.priceToCoordinate(d.p1.price); const y1 = series.priceToCoordinate(d.p1.price);
const x2 = chart.timeScale().timeToCoordinate(d.p2.time); const x2 = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.p2.time));
const y2 = series.priceToCoordinate(d.p2.price); const y2 = series.priceToCoordinate(d.p2.price);
if (x1 !== null && y1 !== null && x2 !== null && y2 !== null) { if (x1 !== null && y1 !== null && x2 !== null && y2 !== null) {
const priceDiff = d.p2.price - d.p1.price; const priceDiff = d.p2.price - d.p1.price;
const percentChange = (priceDiff / d.p1.price) * 100; const percentChange = (priceDiff / d.p1.price) * 100;
const isUp = priceDiff >= 0; const isUp = priceDiff >= 0;
@ -551,8 +610,8 @@ export class DrawingManager {
// 3. Data Calculation // 3. Data Calculation
// Bar count // Bar count
const candleData = this.dashboard.allData.get(this.dashboard.currentInterval) || []; const candleData = this.dashboard.allData.get(this.dashboard.currentInterval) || [];
const startIdx = candleData.findIndex(c => c.time === d.p1.time); const startIdx = candleData.findIndex(c => c.time === this.snapToNearestTime(d.p1.time));
const endIdx = candleData.findIndex(c => c.time === d.p2.time); const endIdx = candleData.findIndex(c => c.time === this.snapToNearestTime(d.p2.time));
let barCount = 0; let barCount = 0;
let totalVol = 0; let totalVol = 0;
if (startIdx !== -1 && endIdx !== -1) { if (startIdx !== -1 && endIdx !== -1) {
@ -564,7 +623,8 @@ export class DrawingManager {
} }
} else { } else {
// Rough estimate if not in current data // Rough estimate if not in current data
barCount = Math.round((d.p2.time - d.p1.time) / 60); // Assuming 1m? Better use current interval const intervalSeconds = 60; // Default fallback
barCount = Math.round((d.p2.time - d.p1.time) / intervalSeconds);
} }
// Time duration // Time duration