118 lines
3.9 KiB
JavaScript
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();
|
|
}
|
|
}
|