Files
winterfail/js/ui/markers-plugin.js

118 lines
3.9 KiB
JavaScript

export class SeriesMarkersPrimitive {
constructor(markers) {
this._markers = markers || [];
this._paneViews = [new MarkersPaneView(this)];
}
setMarkers(markers) {
this._markers = markers;
if (this._requestUpdate) {
this._requestUpdate();
}
}
attached(param) {
this._chart = param.chart;
this._series = param.series;
this._requestUpdate = param.requestUpdate;
this._requestUpdate();
}
detached() {
this._chart = undefined;
this._series = undefined;
this._requestUpdate = undefined;
}
updateAllViews() {}
paneViews() {
return this._paneViews;
}
}
class MarkersPaneView {
constructor(source) {
this._source = source;
}
renderer() {
return new MarkersRenderer(this._source);
}
}
class MarkersRenderer {
constructor(source) {
this._source = source;
}
draw(target) {
if (!this._source._chart || !this._source._series) return;
// Lightweight Charts v5 wraps context
const ctx = target.context;
const series = this._source._series;
const chart = this._source._chart;
const markers = this._source._markers;
ctx.save();
// Ensure markers are sorted by time (usually already done)
for (const marker of markers) {
const timeCoordinate = chart.timeScale().timeToCoordinate(marker.time);
if (timeCoordinate === null) continue;
// To position above or below bar, we need the candle data or we use the marker.value if provided
// For true aboveBar/belowBar without candle data, we might just use series.priceToCoordinate on marker.value
let price = marker.value;
// Fallbacks if no value provided (which our calculator does provide)
if (!price) continue;
const priceCoordinate = series.priceToCoordinate(price);
if (priceCoordinate === null) continue;
const x = timeCoordinate;
const size = 5;
const margin = 12; // Gap between price and marker
const isAbove = marker.position === 'aboveBar';
const y = isAbove ? priceCoordinate - margin : priceCoordinate + margin;
ctx.fillStyle = marker.color || '#26a69a';
ctx.beginPath();
if (marker.shape === 'arrowUp' || (!marker.shape && !isAbove)) {
ctx.moveTo(x, y - size);
ctx.lineTo(x - size, y + size);
ctx.lineTo(x + size, y + size);
} else if (marker.shape === 'arrowDown' || (!marker.shape && isAbove)) {
ctx.moveTo(x, y + size);
ctx.lineTo(x - size, y - size);
ctx.lineTo(x + size, y - size);
} else if (marker.shape === 'circle') {
ctx.arc(x, y, size, 0, Math.PI * 2);
} else if (marker.shape === 'square') {
ctx.rect(x - size, y - size, size * 2, size * 2);
} else if (marker.shape === 'custom' && marker.text) {
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(marker.text, x, y);
continue;
} else {
// Default triangle
if (isAbove) {
ctx.moveTo(x, y + size);
ctx.lineTo(x - size, y - size);
ctx.lineTo(x + size, y - size);
} else {
ctx.moveTo(x, y - size);
ctx.lineTo(x - size, y + size);
ctx.lineTo(x + size, y + size);
}
}
ctx.fill();
}
ctx.restore();
}
}