fix: implement touch support for mobile and fix timeframe-based data calculation bugs
This commit is contained in:
@ -29,10 +29,26 @@ export class DrawingManager {
|
||||
init() {
|
||||
const container = this.container;
|
||||
|
||||
// Mouse Events
|
||||
container.addEventListener('mousedown', (e) => this.handleMouseDown(e));
|
||||
window.addEventListener('mousemove', (e) => this.handleMouseMove(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) => {
|
||||
if ((e.key === 'Delete' || e.key === 'Backspace') && this.selectedDrawing) {
|
||||
this.drawings = this.drawings.filter(d => d !== this.selectedDrawing);
|
||||
@ -114,6 +130,16 @@ export class DrawingManager {
|
||||
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() {
|
||||
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();
|
||||
}
|
||||
|
||||
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) {
|
||||
target.useMediaCoordinateSpace((scope) => {
|
||||
const ctx = scope.context;
|
||||
@ -393,9 +445,9 @@ export class DrawingManager {
|
||||
if (isSelected) { ctx.shadowBlur = 5; ctx.shadowColor = d.color; }
|
||||
|
||||
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 x2 = chart.timeScale().timeToCoordinate(d.p2.time);
|
||||
const x2 = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.p2.time));
|
||||
const y2 = series.priceToCoordinate(d.p2.price);
|
||||
if (x1 !== null && y1 !== null && x2 !== null && y2 !== null) {
|
||||
ctx.beginPath();
|
||||
@ -414,9 +466,9 @@ export class DrawingManager {
|
||||
}
|
||||
}
|
||||
} 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 x2 = chart.timeScale().timeToCoordinate(d.p2.time);
|
||||
const x2 = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.p2.time));
|
||||
const y2 = series.priceToCoordinate(d.p2.price);
|
||||
if (x1 !== null && y1 !== null && x2 !== null && y2 !== null) {
|
||||
ctx.fillStyle = d.color + '22';
|
||||
@ -431,17 +483,23 @@ export class DrawingManager {
|
||||
} else if (d.type === 'horizontal_line') {
|
||||
const y = series.priceToCoordinate(d.price);
|
||||
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') {
|
||||
const x = chart.timeScale().timeToCoordinate(d.time);
|
||||
const x = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.time));
|
||||
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') {
|
||||
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 x2 = chart.timeScale().timeToCoordinate(d.p2.time);
|
||||
const x2 = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.p2.time));
|
||||
const y2 = series.priceToCoordinate(d.p2.price);
|
||||
if (x1 !== null && y1 !== null && x2 !== null && y2 !== null) {
|
||||
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') {
|
||||
const x = chart.timeScale().timeToCoordinate(d.time);
|
||||
const x = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.time));
|
||||
const y = series.priceToCoordinate(d.price);
|
||||
if (x !== null && y !== null) {
|
||||
ctx.fillStyle = d.color; ctx.beginPath();
|
||||
@ -483,7 +541,7 @@ export class DrawingManager {
|
||||
if (isSelected) { ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 1; ctx.stroke(); }
|
||||
}
|
||||
} 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);
|
||||
if (x !== null && y !== null) {
|
||||
ctx.fillStyle = d.color;
|
||||
@ -496,12 +554,13 @@ export class DrawingManager {
|
||||
}
|
||||
}
|
||||
} 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 x2 = chart.timeScale().timeToCoordinate(d.p2.time);
|
||||
const x2 = chart.timeScale().timeToCoordinate(this.snapToNearestTime(d.p2.time));
|
||||
const y2 = series.priceToCoordinate(d.p2.price);
|
||||
|
||||
if (x1 !== null && y1 !== null && x2 !== null && y2 !== null) {
|
||||
|
||||
const priceDiff = d.p2.price - d.p1.price;
|
||||
const percentChange = (priceDiff / d.p1.price) * 100;
|
||||
const isUp = priceDiff >= 0;
|
||||
@ -551,8 +610,8 @@ export class DrawingManager {
|
||||
// 3. Data Calculation
|
||||
// Bar count
|
||||
const candleData = this.dashboard.allData.get(this.dashboard.currentInterval) || [];
|
||||
const startIdx = candleData.findIndex(c => c.time === d.p1.time);
|
||||
const endIdx = candleData.findIndex(c => c.time === d.p2.time);
|
||||
const startIdx = candleData.findIndex(c => c.time === this.snapToNearestTime(d.p1.time));
|
||||
const endIdx = candleData.findIndex(c => c.time === this.snapToNearestTime(d.p2.time));
|
||||
let barCount = 0;
|
||||
let totalVol = 0;
|
||||
if (startIdx !== -1 && endIdx !== -1) {
|
||||
@ -564,7 +623,8 @@ export class DrawingManager {
|
||||
}
|
||||
} else {
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user