Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/Context.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ export class Context {
public lang: any;
public length: number = 0;

/** References to drawing helpers for streaming rollback */
public _drawingHelpers: { rollbackFromBar(barIdx: number): void }[] = [];
/** References to drawing helpers for streaming rollback and plot sync */
public _drawingHelpers: { rollbackFromBar(barIdx: number): void; syncToPlot?(): void }[] = [];

// Combined namespace and core functions - the default way to access everything
public pine: {
Expand Down Expand Up @@ -444,9 +444,6 @@ export class Context {
get: () => polylineHelper.all,
});

// Register drawing helpers for streaming rollback
this._drawingHelpers = [labelHelper, lineHelper, boxHelper, linefillHelper, polylineHelper];

// table namespace
const tableHelper = new TableHelper(this);
this.bindContextObject(
Expand Down Expand Up @@ -482,6 +479,9 @@ export class Context {
get: () => tableHelper.all,
});

// Register all drawing helpers for streaming rollback and plot sync
this._drawingHelpers = [labelHelper, lineHelper, boxHelper, linefillHelper, polylineHelper, tableHelper];

// color namespace
const colorHelper = new PineColor(this);
this.bindContextObject(
Expand Down
7 changes: 7 additions & 0 deletions src/PineTS.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,13 @@ export class PineTS {
context.result.push(result);
}

// Sync drawing object plots after all mutations for this bar.
// Serializes the current state of labels/lines/boxes/etc. into plain objects
// so that context.plots contains safe, JSON-serializable data.
for (const helper of context._drawingHelpers) {
if (helper.syncToPlot) helper.syncToPlot();
}

//shift context
const shiftVariables = (container: any) => {
for (let ctxVarName of contextVarNames) {
Expand Down
24 changes: 23 additions & 1 deletion src/namespaces/Plots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ const PLOT_SIGNATURE = [
'join', 'editable', 'show_last', 'display', 'format', 'precision', 'force_overlay',
];

//prettier-ignore
const PLOTCHAR_SIGNATURE = [
'series', 'title', 'char', 'location', 'color', 'offset', 'text', 'textcolor',
'editable', 'size', 'show_last', 'display', 'format', 'precision', 'force_overlay',
];

//prettier-ignore
const PLOTCHAR_ARGS_TYPES = {
series: 'series', title: 'string', char: 'string', location: 'string',
color: 'color', offset: 'number', text: 'string', textcolor: 'color',
editable: 'boolean', size: 'string', show_last: 'number', display: 'string',
format: 'string', precision: 'number', force_overlay: 'boolean',
};

//prettier-ignore
const PLOT_SHAPE_SIGNATURE = [
'series', 'title', 'style', 'location', 'color', 'offset', 'text', 'textcolor',
Expand Down Expand Up @@ -193,7 +207,7 @@ export class PlotHelper {
//in the current implementation, plot functions are only used to collect data for the plots array and map it to the market data
plotchar(...args) {
const callsiteId = extractCallsiteId(args);
const _parsed = parseArgsForPineParams<PlotOptions>(args, PLOT_SIGNATURE, PLOT_ARGS_TYPES);
const _parsed = parseArgsForPineParams<PlotCharOptions>(args, PLOTCHAR_SIGNATURE, PLOTCHAR_ARGS_TYPES);
const { series, title, ...others } = _parsed;
const options = this.extractPlotOptions(others);
const plotKey = this._resolvePlotKey(title, callsiteId);
Expand All @@ -208,6 +222,14 @@ export class PlotHelper {
title,
time: this.context.marketData[this.context.idx].openTime,
value: value,
options: {
char: options.char,
color: options.color,
textcolor: options.textcolor,
location: options.location,
size: options.size,
offset: options.offset,
},
});
return this.context.plots[plotKey];
}
Expand Down
10 changes: 5 additions & 5 deletions src/namespaces/box/BoxHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ export class BoxHelper {
}
}

private _syncToPlot() {
public syncToPlot() {
this._ensurePlotsEntry();
const time = this.context.marketData[0]?.openTime || 0;
this.context.plots['__boxes__'].data = [{
time,
value: this._boxes,
value: this._boxes.map(bx => bx.toPlotData()),
options: { style: 'drawing_box' },
}];
}
Expand Down Expand Up @@ -117,7 +117,7 @@ export class BoxHelper {
b._helper = this;
b._createdAtBar = this.context.idx;
this._boxes.push(b);
this._syncToPlot();
this.syncToPlot();
return b;
}

Expand Down Expand Up @@ -309,7 +309,7 @@ export class BoxHelper {
b._helper = this;
b._createdAtBar = this.context.idx;
this._boxes.push(b);
this._syncToPlot();
this.syncToPlot();
return b;
}

Expand All @@ -328,6 +328,6 @@ export class BoxHelper {
*/
rollbackFromBar(barIdx: number): void {
this._boxes = this._boxes.filter((b) => b._createdAtBar < barIdx);
this._syncToPlot();
this.syncToPlot();
}
}
14 changes: 14 additions & 0 deletions src/namespaces/box/BoxObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,20 @@ export class BoxObject {
this._deleted = true;
}

toPlotData(): any {
return {
id: this.id,
left: this.left, top: this.top, right: this.right, bottom: this.bottom,
xloc: this.xloc, extend: this.extend,
border_color: this.border_color, border_style: this.border_style, border_width: this.border_width,
bgcolor: this.bgcolor,
text: this.text, text_color: this.text_color, text_size: this.text_size,
text_halign: this.text_halign, text_valign: this.text_valign,
text_wrap: this.text_wrap, text_font_family: this.text_font_family, text_formatting: this.text_formatting,
force_overlay: this.force_overlay, _deleted: this._deleted,
};
}

copy(): BoxObject {
const b = new BoxObject(
this.left, this.top, this.right, this.bottom,
Expand Down
5 changes: 5 additions & 0 deletions src/namespaces/color/PineColor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ export class PineColor {
// If not a string (e.g. NaN for na), return as-is
if (!color || typeof color !== 'string') return color;

// Treat NaN transparency as "no transparency specified" (keep original color)
if (typeof a === 'number' && isNaN(a)) a = undefined;

// Handle hexadecimal colors
if (color.startsWith('#')) {
const hex = color.slice(1);
Expand Down Expand Up @@ -164,6 +167,8 @@ export class PineColor {

// ── color.rgb(r, g, b, a?) ────────────────────────────────────────
rgb(r: number, g: number, b: number, a?: number) {
// Treat NaN transparency as "no transparency" (fully opaque)
if (typeof a === 'number' && isNaN(a)) a = undefined;
return a != null ? `rgba(${r}, ${g}, ${b}, ${(100 - a) / 100})` : `rgb(${r}, ${g}, ${b})`;
}

Expand Down
4 changes: 2 additions & 2 deletions src/namespaces/input/methods/bool.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0-only

import { parseInputOptions } from '../utils';
import { parseInputOptions, resolveInput } from '../utils';

export function bool(context: any) {
return (...args: any[]) => {
const options = parseInputOptions(args);
return options.defval;
return resolveInput(context, options);
};
}
10 changes: 5 additions & 5 deletions src/namespaces/label/LabelHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class LabelHelper {
}
}

private _syncToPlot() {
public syncToPlot() {
this._ensurePlotsEntry();
// Store ALL labels as a single array value at the first bar's time.
// Using a live reference so setter mutations are reflected automatically.
Expand All @@ -51,7 +51,7 @@ export class LabelHelper {
const time = this.context.marketData[0]?.openTime || 0;
this.context.plots['__labels__'].data = [{
time,
value: this._labels,
value: this._labels.map(lbl => lbl.toPlotData()),
options: { style: 'label' },
}];
}
Expand Down Expand Up @@ -115,7 +115,7 @@ export class LabelHelper {
lbl._helper = this;
lbl._createdAtBar = this.context.idx;
this._labels.push(lbl);
this._syncToPlot();
this.syncToPlot();
return lbl;
}

Expand Down Expand Up @@ -248,7 +248,7 @@ export class LabelHelper {
lbl._helper = this;
lbl._createdAtBar = this.context.idx;
this._labels.push(lbl);
this._syncToPlot();
this.syncToPlot();
return lbl;
}

Expand All @@ -268,7 +268,7 @@ export class LabelHelper {
*/
rollbackFromBar(barIdx: number): void {
this._labels = this._labels.filter((l) => l._createdAtBar < barIdx);
this._syncToPlot();
this.syncToPlot();
}

// --- Style constants ---
Expand Down
1 change: 1 addition & 0 deletions src/namespaces/label/LabelObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export class LabelObject {
tooltip: this.tooltip,
text_font_family: this.text_font_family,
force_overlay: this.force_overlay,
_deleted: this._deleted,
};
}
}
10 changes: 5 additions & 5 deletions src/namespaces/line/LineHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class LineHelper {
}
}

private _syncToPlot() {
public syncToPlot() {
this._ensurePlotsEntry();
// Store ALL lines as a single array value at the first bar's time.
// Using a live reference so setter mutations are reflected automatically.
Expand All @@ -48,7 +48,7 @@ export class LineHelper {
const time = this.context.marketData[0]?.openTime || 0;
this.context.plots['__lines__'].data = [{
time,
value: this._lines,
value: this._lines.map(ln => ln.toPlotData()),
options: { style: 'drawing_line' },
}];
}
Expand Down Expand Up @@ -107,7 +107,7 @@ export class LineHelper {
ln._helper = this;
ln._createdAtBar = this.context.idx;
this._lines.push(ln);
this._syncToPlot();
this.syncToPlot();
return ln;
}

Expand Down Expand Up @@ -268,7 +268,7 @@ export class LineHelper {
ln._helper = this;
ln._createdAtBar = this.context.idx;
this._lines.push(ln);
this._syncToPlot();
this.syncToPlot();
return ln;
}

Expand All @@ -288,7 +288,7 @@ export class LineHelper {
*/
rollbackFromBar(barIdx: number): void {
this._lines = this._lines.filter((l) => l._createdAtBar < barIdx);
this._syncToPlot();
this.syncToPlot();
}

// --- Style constants ---
Expand Down
1 change: 1 addition & 0 deletions src/namespaces/line/LineObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export class LineObject {
style: this.style,
width: this.width,
force_overlay: this.force_overlay,
_deleted: this._deleted,
};
}
}
8 changes: 4 additions & 4 deletions src/namespaces/linefill/LinefillHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ export class LinefillHelper {
}
}

private _syncToPlot() {
public syncToPlot() {
this._ensurePlotsEntry();
// Store ALL linefills as a single array value at the first bar's time.
// Same aggregation pattern as lines and labels.
const time = this.context.marketData[0]?.openTime || 0;
this.context.plots['__linefills__'].data = [{
time,
value: this._linefills,
value: this._linefills.map(lf => lf.toPlotData()),
options: { style: 'linefill' },
}];
}
Expand Down Expand Up @@ -68,7 +68,7 @@ export class LinefillHelper {
const lf = new LinefillObject(resolvedLine1, resolvedLine2, resolvedColor);
lf._createdAtBar = this.context.idx;
this._linefills.push(lf);
this._syncToPlot();
this.syncToPlot();
return lf;
}

Expand Down Expand Up @@ -110,6 +110,6 @@ export class LinefillHelper {
*/
rollbackFromBar(barIdx: number): void {
this._linefills = this._linefills.filter((lf) => lf._createdAtBar < barIdx);
this._syncToPlot();
this.syncToPlot();
}
}
19 changes: 19 additions & 0 deletions src/namespaces/linefill/LinefillObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,23 @@ export class LinefillObject {
delete(): void {
this._deleted = true;
}

toPlotData(): any {
// Inline line data so consumers don't need raw LineObject references
const serializeLine = (ln: LineObject | null) => {
if (!ln) return null;
return {
id: ln.id, x1: ln.x1, y1: ln.y1, x2: ln.x2, y2: ln.y2,
xloc: ln.xloc, extend: ln.extend, color: ln.color,
style: ln.style, width: ln.width, _deleted: ln._deleted,
};
};
return {
id: this.id,
line1: serializeLine(this.line1),
line2: serializeLine(this.line2),
color: this.color,
_deleted: this._deleted,
};
}
}
11 changes: 6 additions & 5 deletions src/namespaces/matrix/methods/mult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,22 @@ export function mult(context: Context) {
}
return newMatrix;
} else if (id2 instanceof PineArrayObject || Array.isArray((id2 as any).array || id2)) {
// Vector multiplication
// Vector multiplication — returns a PineArrayObject (flat vector),
// matching TradingView behavior where matrix.mult(vector) → vector.
const vec = (id2 as any).array || (id2 as any);
if (cols1 !== vec.length) {
return new PineMatrixObject(0, 0, NaN, context);
return new PineArrayObject([], 'float' as any, context);
}

const newMatrix = new PineMatrixObject(rows1, 1, 0, context);
const result: number[] = [];
for (let i = 0; i < rows1; i++) {
let sum = 0;
for (let j = 0; j < cols1; j++) {
sum += id.matrix[i][j] * vec[j];
}
newMatrix.matrix[i][0] = sum;
result.push(sum);
}
return newMatrix;
return new PineArrayObject(result, 'float' as any, context);
} else {
// Scalar multiplication
const scalar = id2 as number;
Expand Down
Loading
Loading