min pivot distance only applies to L1 pivots ,
charts minimize addons, delete buttons WIP
This commit is contained in:
@@ -170,7 +170,7 @@ class ChartManager {
|
||||
pivotDots.y.push(pivot.price);
|
||||
pivotDots.text.push(`L${pivot.level} High Pivot<br>Price: $${pivot.price.toFixed(2)}<br>Strength: ${(pivot.strength * 100).toFixed(0)}%`);
|
||||
pivotDots.marker.color.push(color);
|
||||
pivotDots.marker.size.push(8);
|
||||
pivotDots.marker.size.push(this._getPivotMarkerSize(pivot.level));
|
||||
pivotDots.marker.symbol.push('triangle-down');
|
||||
|
||||
// Draw horizontal line ONLY for last pivot of this level
|
||||
@@ -217,7 +217,7 @@ class ChartManager {
|
||||
pivotDots.y.push(pivot.price);
|
||||
pivotDots.text.push(`L${pivot.level} Low Pivot<br>Price: $${pivot.price.toFixed(2)}<br>Strength: ${(pivot.strength * 100).toFixed(0)}%`);
|
||||
pivotDots.marker.color.push(color);
|
||||
pivotDots.marker.size.push(8);
|
||||
pivotDots.marker.size.push(this._getPivotMarkerSize(pivot.level));
|
||||
pivotDots.marker.symbol.push('triangle-up');
|
||||
|
||||
// Draw horizontal line ONLY for last pivot of this level
|
||||
@@ -285,24 +285,42 @@ class ChartManager {
|
||||
annotations: []
|
||||
};
|
||||
|
||||
// Add click handler for chart
|
||||
// Add click handler for chart and annotations
|
||||
plotElement.on('plotly_click', (eventData) => {
|
||||
// Check if this is an annotation click by looking at the clicked element
|
||||
if (eventData.points && eventData.points.length > 0) {
|
||||
const point = eventData.points[0];
|
||||
// If it's a shape click (annotation line), try to find the annotation
|
||||
if (point.data && point.data.name && point.data.name.startsWith('line_')) {
|
||||
const annotationId = point.data.name.replace('line_', '');
|
||||
console.log('Line annotation clicked:', annotationId);
|
||||
this.handleAnnotationClick(annotationId, 'edit');
|
||||
return; // Don't process as regular chart click
|
||||
}
|
||||
}
|
||||
|
||||
// Regular chart click for annotation marking
|
||||
this.handleChartClick(timeframe, eventData);
|
||||
});
|
||||
|
||||
// Add click handler for annotations
|
||||
plotElement.on('plotly_clickannotation', (eventData) => {
|
||||
console.log('Annotation clicked:', eventData);
|
||||
const annotationName = eventData.annotation.name;
|
||||
if (annotationName) {
|
||||
const parts = annotationName.split('_');
|
||||
const action = parts[0]; // 'entry', 'exit', or 'delete'
|
||||
const annotationId = parts[1];
|
||||
|
||||
console.log(`Annotation action: ${action}, ID: ${annotationId}`);
|
||||
|
||||
if (action === 'delete') {
|
||||
this.handleAnnotationClick(annotationId, 'delete');
|
||||
} else {
|
||||
this.handleAnnotationClick(annotationId, 'edit');
|
||||
}
|
||||
} else {
|
||||
console.log('No annotation name found in click event');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -418,7 +436,7 @@ class ChartManager {
|
||||
pivotDots.y.push(pivot.price);
|
||||
pivotDots.text.push(`L${pivot.level} High Pivot<br>Price: $${pivot.price.toFixed(2)}<br>Strength: ${(pivot.strength * 100).toFixed(0)}%`);
|
||||
pivotDots.marker.color.push(color);
|
||||
pivotDots.marker.size.push(8);
|
||||
pivotDots.marker.size.push(this._getPivotMarkerSize(pivot.level));
|
||||
pivotDots.marker.symbol.push('triangle-down');
|
||||
|
||||
// Draw horizontal line ONLY for last pivot
|
||||
@@ -464,7 +482,7 @@ class ChartManager {
|
||||
pivotDots.y.push(pivot.price);
|
||||
pivotDots.text.push(`L${pivot.level} Low Pivot<br>Price: $${pivot.price.toFixed(2)}<br>Strength: ${(pivot.strength * 100).toFixed(0)}%`);
|
||||
pivotDots.marker.color.push(color);
|
||||
pivotDots.marker.size.push(8);
|
||||
pivotDots.marker.size.push(this._getPivotMarkerSize(pivot.level));
|
||||
pivotDots.marker.symbol.push('triangle-up');
|
||||
|
||||
// Draw horizontal line ONLY for last pivot
|
||||
@@ -644,13 +662,32 @@ class ChartManager {
|
||||
});
|
||||
});
|
||||
|
||||
// Update chart layout with annotations
|
||||
// Get existing pivot annotations (they have specific names like L1H, L2L)
|
||||
const existingLayout = chart.element.layout || {};
|
||||
const existingAnnotations = existingLayout.annotations || [];
|
||||
const pivotAnnotations = existingAnnotations.filter(ann =>
|
||||
ann.text && ann.text.match(/^L\d+[HL]$/)
|
||||
);
|
||||
|
||||
// Merge pivot annotations with trade annotations
|
||||
const allAnnotations = [...pivotAnnotations, ...plotlyAnnotations];
|
||||
|
||||
// Get existing pivot shapes
|
||||
const existingShapes = existingLayout.shapes || [];
|
||||
const pivotShapes = existingShapes.filter(shape =>
|
||||
shape.layer === 'below' && shape.line && shape.line.dash === 'dash'
|
||||
);
|
||||
|
||||
// Merge pivot shapes with trade annotation shapes
|
||||
const allShapes = [...pivotShapes, ...plotlyShapes];
|
||||
|
||||
// Update chart layout with merged annotations and shapes
|
||||
Plotly.relayout(chart.plotId, {
|
||||
annotations: plotlyAnnotations,
|
||||
shapes: plotlyShapes
|
||||
annotations: allAnnotations,
|
||||
shapes: allShapes
|
||||
});
|
||||
|
||||
console.log(`Updated ${timeframeAnnotations.length} annotations for ${timeframe}`);
|
||||
console.log(`Updated ${timeframeAnnotations.length} trade annotations for ${timeframe} (preserved ${pivotAnnotations.length} pivot annotations)`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -713,83 +750,217 @@ class ChartManager {
|
||||
const annotation = this.annotations[annotationId];
|
||||
if (!annotation) return;
|
||||
|
||||
// Show edit dialog
|
||||
const action = prompt(
|
||||
'Edit annotation:\n' +
|
||||
'1 - Move entry point\n' +
|
||||
'2 - Move exit point\n' +
|
||||
'3 - Delete annotation\n' +
|
||||
'Enter choice (1-3):',
|
||||
'1'
|
||||
);
|
||||
// Create a better edit dialog using Bootstrap modal
|
||||
this.showEditDialog(annotationId, annotation);
|
||||
}
|
||||
|
||||
if (action === '1') {
|
||||
// Move entry point
|
||||
window.showSuccess('Click on chart to set new entry point');
|
||||
/**
|
||||
* Show edit dialog for annotation
|
||||
*/
|
||||
showEditDialog(annotationId, annotation) {
|
||||
// Create modal HTML
|
||||
const modalHtml = `
|
||||
<div class="modal fade" id="editAnnotationModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Edit Annotation</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<strong>Current Annotation:</strong>
|
||||
<div class="mt-2">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<small class="text-muted">Entry:</small><br>
|
||||
<span class="badge bg-success">▲</span>
|
||||
${new Date(annotation.entry.timestamp).toLocaleString()}<br>
|
||||
<small>$${annotation.entry.price.toFixed(2)}</small>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<small class="text-muted">Exit:</small><br>
|
||||
<span class="badge bg-danger">▼</span>
|
||||
${new Date(annotation.exit.timestamp).toLocaleString()}<br>
|
||||
<small>$${annotation.exit.price.toFixed(2)}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<small class="text-muted">P&L:</small>
|
||||
<span class="badge ${annotation.profit_loss_pct >= 0 ? 'bg-success' : 'bg-danger'}">
|
||||
${annotation.profit_loss_pct >= 0 ? '+' : ''}${annotation.profit_loss_pct.toFixed(2)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">What would you like to edit?</label>
|
||||
<div class="btn-group w-100" role="group">
|
||||
<button type="button" class="btn btn-outline-primary" id="edit-entry-btn">
|
||||
<i class="fas fa-arrow-up"></i> Move Entry
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary" id="edit-exit-btn">
|
||||
<i class="fas fa-arrow-down"></i> Move Exit
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger" id="delete-annotation-btn">
|
||||
<i class="fas fa-trash"></i> Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Store annotation for editing
|
||||
if (window.appState && window.appState.annotationManager) {
|
||||
window.appState.annotationManager.editingAnnotation = {
|
||||
annotation_id: annotationId,
|
||||
original: annotation,
|
||||
editMode: 'entry'
|
||||
};
|
||||
|
||||
// Remove current annotation from display
|
||||
this.removeAnnotation(annotationId);
|
||||
|
||||
// Show exit marker as reference
|
||||
const chart = this.charts[annotation.timeframe];
|
||||
if (chart) {
|
||||
Plotly.relayout(chart.plotId, {
|
||||
annotations: [{
|
||||
x: annotation.exit.timestamp,
|
||||
y: annotation.exit.price,
|
||||
text: '▼ (exit)',
|
||||
showarrow: true,
|
||||
arrowhead: 2,
|
||||
ax: 0,
|
||||
ay: 40,
|
||||
font: { size: 14, color: '#9ca3af' }
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (action === '2') {
|
||||
// Move exit point
|
||||
window.showSuccess('Click on chart to set new exit point');
|
||||
|
||||
if (window.appState && window.appState.annotationManager) {
|
||||
window.appState.annotationManager.editingAnnotation = {
|
||||
annotation_id: annotationId,
|
||||
original: annotation,
|
||||
editMode: 'exit'
|
||||
};
|
||||
|
||||
// Remove current annotation from display
|
||||
this.removeAnnotation(annotationId);
|
||||
|
||||
// Show entry marker as reference
|
||||
const chart = this.charts[annotation.timeframe];
|
||||
if (chart) {
|
||||
Plotly.relayout(chart.plotId, {
|
||||
annotations: [{
|
||||
x: annotation.entry.timestamp,
|
||||
y: annotation.entry.price,
|
||||
text: '▲ (entry)',
|
||||
showarrow: true,
|
||||
arrowhead: 2,
|
||||
ax: 0,
|
||||
ay: -40,
|
||||
font: { size: 14, color: '#9ca3af' }
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (action === '3') {
|
||||
// Delete
|
||||
this.handleAnnotationClick(annotationId, 'delete');
|
||||
// Remove existing modal if any
|
||||
const existingModal = document.getElementById('editAnnotationModal');
|
||||
if (existingModal) {
|
||||
existingModal.remove();
|
||||
}
|
||||
|
||||
// Add modal to page
|
||||
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||||
|
||||
// Show modal
|
||||
const modal = new bootstrap.Modal(document.getElementById('editAnnotationModal'));
|
||||
modal.show();
|
||||
|
||||
// Add event listeners
|
||||
document.getElementById('edit-entry-btn').addEventListener('click', () => {
|
||||
modal.hide();
|
||||
this.startEditMode(annotationId, annotation, 'entry');
|
||||
});
|
||||
|
||||
document.getElementById('edit-exit-btn').addEventListener('click', () => {
|
||||
modal.hide();
|
||||
this.startEditMode(annotationId, annotation, 'exit');
|
||||
});
|
||||
|
||||
document.getElementById('delete-annotation-btn').addEventListener('click', () => {
|
||||
modal.hide();
|
||||
this.handleAnnotationClick(annotationId, 'delete');
|
||||
});
|
||||
|
||||
// Clean up modal when hidden
|
||||
document.getElementById('editAnnotationModal').addEventListener('hidden.bs.modal', () => {
|
||||
document.getElementById('editAnnotationModal').remove();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start edit mode for annotation
|
||||
*/
|
||||
startEditMode(annotationId, annotation, editMode) {
|
||||
const message = editMode === 'entry' ? 'Click on chart to set new entry point' : 'Click on chart to set new exit point';
|
||||
window.showSuccess(message);
|
||||
|
||||
// Store annotation for editing
|
||||
if (window.appState && window.appState.annotationManager) {
|
||||
window.appState.annotationManager.editingAnnotation = {
|
||||
annotation_id: annotationId,
|
||||
original: annotation,
|
||||
editMode: editMode
|
||||
};
|
||||
|
||||
// Remove current annotation from display
|
||||
this.removeAnnotation(annotationId);
|
||||
|
||||
// Show reference marker
|
||||
const chart = this.charts[annotation.timeframe];
|
||||
if (chart) {
|
||||
const referencePoint = editMode === 'entry' ? annotation.exit : annotation.entry;
|
||||
const markerText = editMode === 'entry' ? '▼ (exit)' : '▲ (entry)';
|
||||
const arrowDirection = editMode === 'entry' ? 40 : -40;
|
||||
|
||||
Plotly.relayout(chart.plotId, {
|
||||
annotations: [{
|
||||
x: referencePoint.timestamp,
|
||||
y: referencePoint.price,
|
||||
text: markerText,
|
||||
showarrow: true,
|
||||
arrowhead: 2,
|
||||
ax: 0,
|
||||
ay: arrowDirection,
|
||||
font: { size: 14, color: '#9ca3af' },
|
||||
bgcolor: 'rgba(31, 41, 55, 0.8)',
|
||||
borderpad: 4
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all annotations from charts
|
||||
*/
|
||||
clearAllAnnotations() {
|
||||
console.log('Clearing all annotations from charts');
|
||||
|
||||
// Clear from memory
|
||||
this.annotations = {};
|
||||
|
||||
// Update all charts
|
||||
Object.keys(this.charts).forEach(timeframe => {
|
||||
this.updateChartAnnotations(timeframe);
|
||||
});
|
||||
|
||||
console.log('All annotations cleared from charts');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update chart layout when charts are minimized/maximized
|
||||
*/
|
||||
updateChartLayout() {
|
||||
const chartContainer = document.getElementById('chart-container');
|
||||
const visibleCharts = document.querySelectorAll('.timeframe-chart:not(.minimized)');
|
||||
const minimizedCharts = document.querySelectorAll('.timeframe-chart.minimized');
|
||||
|
||||
// Remove scroll if all charts are visible or if some are minimized
|
||||
if (minimizedCharts.length > 0) {
|
||||
chartContainer.classList.add('no-scroll');
|
||||
|
||||
// Calculate available height for visible charts
|
||||
const containerHeight = chartContainer.clientHeight;
|
||||
const headerHeight = 50; // Approximate header height
|
||||
const availableHeight = containerHeight - (visibleCharts.length * headerHeight);
|
||||
const chartHeight = Math.max(200, availableHeight / visibleCharts.length);
|
||||
|
||||
// Update visible chart heights
|
||||
visibleCharts.forEach(chart => {
|
||||
const plotElement = chart.querySelector('.chart-plot');
|
||||
if (plotElement) {
|
||||
plotElement.style.height = `${chartHeight}px`;
|
||||
|
||||
// Trigger Plotly resize
|
||||
const plotId = plotElement.id;
|
||||
if (plotId) {
|
||||
Plotly.Plots.resize(plotId);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
chartContainer.classList.remove('no-scroll');
|
||||
|
||||
// Reset to default heights
|
||||
visibleCharts.forEach(chart => {
|
||||
const plotElement = chart.querySelector('.chart-plot');
|
||||
if (plotElement) {
|
||||
plotElement.style.height = '300px';
|
||||
|
||||
// Trigger Plotly resize
|
||||
const plotId = plotElement.id;
|
||||
if (plotId) {
|
||||
Plotly.Plots.resize(plotId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Updated chart layout: ${visibleCharts.length} visible, ${minimizedCharts.length} minimized`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -804,6 +975,15 @@ class ChartManager {
|
||||
return colors[Math.min(level - 1, colors.length - 1)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get marker size for pivot level
|
||||
* L1 = smallest (6px), L5 = largest (14px)
|
||||
*/
|
||||
_getPivotMarkerSize(level) {
|
||||
const sizes = [6, 8, 10, 12, 14]; // L1 to L5
|
||||
return sizes[Math.min(level - 1, sizes.length - 1)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable crosshair cursor
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user