From e3999e6506b186e81b69b5b514d5c0a3d0a29522 Mon Sep 17 00:00:00 2001 From: DiTus Date: Sat, 21 Mar 2026 23:56:41 +0100 Subject: [PATCH] fix: Ensure horizontal/vertical line text is visible on screen - Add bounds checking to only render text when line is on screen - Fix text positioning to be consistent with line position --- js/ui/drawing-tools.js | 126 ++++++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 52 deletions(-) diff --git a/js/ui/drawing-tools.js b/js/ui/drawing-tools.js index ac899bc..3f2eeab 100644 --- a/js/ui/drawing-tools.js +++ b/js/ui/drawing-tools.js @@ -819,7 +819,7 @@ export class DrawingManager { } } else if (d.type === 'horizontal_line') { const y = series.priceToCoordinate(d.price); - if (y !== null) { + if (y !== null && y >= 0 && y <= scope.mediaSize.height) { ctx.save(); ctx.strokeStyle = d.opacity !== undefined ? this.hexToRgba(d.color, d.opacity) : d.color; ctx.lineWidth = d.width || 2; @@ -837,51 +837,72 @@ export class DrawingManager { ctx.beginPath(); ctx.arc(0, y, 5, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); } - // Render Text if present + // Store label position for hit detection (before rendering) + let textX = scope.mediaSize.width / 2; + let textY = y; + let labelPos = null; if (d.text) { - ctx.save(); - ctx.setLineDash([]); const fontSize = d.fontSize || 14; const fontStyle = (d.bold ? 'bold ' : '') + (d.italic ? 'italic ' : ''); - ctx.font = `${fontStyle}${fontSize}px Inter`; - ctx.fillStyle = d.textColor || d.color; - const tx = scope.mediaSize.width / 2; - const ty = y; - const offset = 10; - const finalTy = d.alignVert === 'top' ? ty - offset : (d.alignVert === 'bottom' ? ty + offset : ty); - - ctx.textAlign = d.alignHorz || 'center'; - ctx.textBaseline = d.alignVert === 'middle' ? 'middle' : (d.alignVert === 'bottom' ? 'top' : 'bottom'); - ctx.fillText(d.text, tx, finalTy); - ctx.restore(); - } - - // Store label position for hit detection - if (d.text) { - ctx.save(); - ctx.setLineDash([]); - const fontSize = d.fontSize || 14; - const fontStyle = (d.bold ? 'bold ' : '') + (d.italic ? 'italic ' : ''); - ctx.font = `${fontStyle}${fontSize}px Inter`; + const font = `${fontStyle}${fontSize}px Inter`; + ctx.font = font; const metrics = ctx.measureText(d.text); const labelWidth = metrics.width + 16; const labelHeight = 24; const defaultLabelX = scope.mediaSize.width / 2 - labelWidth / 2; + const offset = 10; const defaultLabelY = d.alignVert === 'top' ? y - 40 : (d.alignVert === 'bottom' ? y + 20 : y - 12); - d.labelPos = { + labelPos = { x: defaultLabelX + (d.labelOffset?.x || 0), y: defaultLabelY + (d.labelOffset?.y || 0), width: labelWidth, height: labelHeight }; + textX = scope.mediaSize.width / 2; + textY = d.alignVert === 'top' ? y - offset : (d.alignVert === 'bottom' ? y + offset : y); + } + + // Render Text if present and visible on screen + if (d.text && labelPos && labelPos.y >= 0 && labelPos.y <= scope.mediaSize.height) { + ctx.save(); + ctx.setLineDash([]); + ctx.font = font; + ctx.fillStyle = d.textColor || d.color; + + ctx.textAlign = d.alignHorz || 'center'; + ctx.textBaseline = d.alignVert === 'middle' ? 'middle' : (d.alignVert === 'bottom' ? 'top' : 'bottom'); + ctx.fillText(d.text, textX, textY); ctx.restore(); } + // Store label position for hit detection + if (labelPos) { + d.labelPos = labelPos; + } + + // Render Text if present + if (d.text) { + ctx.save(); + ctx.setLineDash([]); + ctx.font = `${fontStyle}${fontSize}px Inter`; + ctx.fillStyle = d.textColor || d.color; + + ctx.textAlign = d.alignHorz || 'center'; + ctx.textBaseline = d.alignVert === 'middle' ? 'middle' : (d.alignVert === 'bottom' ? 'top' : 'bottom'); + ctx.fillText(d.text, textX, textY); + ctx.restore(); + } + + // Store label position for hit detection + if (labelPos) { + d.labelPos = labelPos; + } + ctx.restore(); } } else if (d.type === 'vertical_line') { const x = this.timeToX(d.time); - if (x !== null) { + if (x !== null && x >= 0 && x <= scope.mediaSize.width) { ctx.save(); ctx.strokeStyle = d.opacity !== undefined ? this.hexToRgba(d.color, d.opacity) : d.color; ctx.lineWidth = d.width || 2; @@ -899,46 +920,47 @@ export class DrawingManager { ctx.beginPath(); ctx.arc(x, 0, 5, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); } - // Render Text if present + // Store label position for hit detection (before rendering) + let textX = x; + let textY = 0; + let labelPos = null; if (d.text) { - ctx.save(); - ctx.setLineDash([]); const fontSize = d.fontSize || 14; const fontStyle = (d.bold ? 'bold ' : '') + (d.italic ? 'italic ' : ''); - ctx.font = `${fontStyle}${fontSize}px Inter`; - ctx.fillStyle = d.textColor || d.color; - const tx = x; - const ty = 0; - const offset = 10; - const finalTy = d.alignVert === 'top' ? ty - offset : (d.alignVert === 'bottom' ? ty + offset : ty); - - ctx.textAlign = d.alignHorz || 'center'; - ctx.textBaseline = d.alignVert === 'middle' ? 'middle' : (d.alignVert === 'bottom' ? 'top' : 'bottom'); - ctx.fillText(d.text, tx, finalTy); - ctx.restore(); - } - - // Store label position for hit detection - if (d.text) { - ctx.save(); - ctx.setLineDash([]); - const fontSize = d.fontSize || 14; - const fontStyle = (d.bold ? 'bold ' : '') + (d.italic ? 'italic ' : ''); - ctx.font = `${fontStyle}${fontSize}px Inter`; + const font = `${fontStyle}${fontSize}px Inter`; + ctx.font = font; const metrics = ctx.measureText(d.text); const labelWidth = metrics.width + 16; const labelHeight = 24; - const defaultLabelX = x - labelWidth / 2; + const offset = 10; const defaultLabelY = d.alignVert === 'top' ? -50 : (d.alignVert === 'bottom' ? 20 : -12); - d.labelPos = { - x: defaultLabelX + (d.labelOffset?.x || 0), + labelPos = { + x: x - labelWidth / 2 + (d.labelOffset?.x || 0), y: defaultLabelY + (d.labelOffset?.y || 0), width: labelWidth, height: labelHeight }; + textY = d.alignVert === 'top' ? -offset : (d.alignVert === 'bottom' ? offset : 0); + } + + // Render Text if present and visible on screen + if (d.text && labelPos && labelPos.y >= 0 && labelPos.y <= scope.mediaSize.height) { + ctx.save(); + ctx.setLineDash([]); + ctx.font = font; + ctx.fillStyle = d.textColor || d.color; + + ctx.textAlign = d.alignHorz || 'center'; + ctx.textBaseline = d.alignVert === 'middle' ? 'middle' : (d.alignVert === 'bottom' ? 'top' : 'bottom'); + ctx.fillText(d.text, textX, textY); ctx.restore(); } + // Store label position for hit detection + if (labelPos) { + d.labelPos = labelPos; + } + ctx.restore(); } } else if (d.type === 'fib_retracement') {