Appearance
Drawings and tables
How do you put a label, a line, a box or a table on the chart and change it later? Each *.new() call makes a drawing and returns its id. Keep the id in a var and call the setters on later bars to move or restyle that one drawing, or call *.new() again to add another. The chart keeps the newest 50 drawings of each kind unless your declaration asks for more.
pine
//@version=6
indicator("20-bar high", overlay = true)
float hi = ta.highest(high, 20)
// var: one label and one line for the whole run, made on the first bar
var label tag = label.new(bar_index, hi, "", color = color.teal,
style = label.style_label_down, textcolor = color.white)
var line level = line.new(bar_index, hi, bar_index + 1, hi,
color = color.teal, extend = extend.right)
if barstate.islast
tag.set_xy(bar_index, hi)
tag.set_text("High " + str.tostring(hi, format.mintick))
level.set_xy1(bar_index - 19, hi)
level.set_xy2(bar_index, hi)A drawing lives on the chart, not in a series: it has no value on each bar, and there is one of it, so a later bar that moves it moves it for the whole chart. That makes drawings right for what describes the present, such as a level, a note or a table, and plots right for what each bar had. For plots, see Plots and colours.
Making and changing a drawing
label.new(), line.new(), box.new(), polyline.new(), linefill.new() and table.new() each return an id of their own type. The kind's setters change one property, such as label.set_text(id, text) or line.set_color(id, color), and its getters read one back. Every setter and getter is also a method, so tag.set_text("…") and label.set_text(tag, "…") are the same call.
Where you keep the id decides how many drawings you get:
| You write | You get |
|---|---|
var label tag = label.new(...), then setters | One label, made on the first bar and moved after |
label.new(...) on every bar | A new label each bar, until the limit below starts removing the oldest |
label.new(...) inside if cond | One label on each bar where cond holds |
A drawing's position takes two numbers. By default x is a bar index, so bar_index - 10 is ten bars back. With xloc = xloc.bar_time, x is a time in milliseconds, which suits a moment you know by its time, such as a session's open. Labels, lines, boxes and polylines can also take chart.points, which carry both: chart.point.from_index(bar_index, high), chart.point.from_time(time, high), or chart.point.now(high) for this bar.
How many stay
Labels, lines, boxes and polylines each count separately. When a script makes more of one kind than its limit, the engine removes the oldest until that many are left, so the chart always shows the newest. The limit is 50 of each, unless indicator() or strategy() says otherwise:
pine
//@version=6
// up to 200 labels stay, the newest
indicator("A label on every cross", overlay = true, max_labels_count = 200)
float fast = ta.ema(close, 9)
float slow = ta.ema(close, 21)
if ta.crossover(fast, slow)
label.new(bar_index, low, "Up", style = label.style_label_up)max_labels_count,max_lines_count,max_boxes_countandmax_polylines_countset each kind's limit. This engine takes any positive number. TradingView's manual allows at most 500.- The engine lets a kind run up to five past its limit before it removes the oldest, so
label.allcan hold a few more than the limit while the chart shows the newest. - A drawing that was removed this way is off the chart and out of
label.all, but its getters still read its last values. - Tables don't count toward a limit. A table made at a position where another table stands replaces it, so one position holds one table.
- A linefill made on the same two lines as another replaces it, and a linefill goes when either of its lines is deleted.
Deleting and copying
label.delete(id) takes a label off the chart and out of label.all at once, and from then on its getters read na. Each kind has the same call. label.copy(id) makes a new drawing with the same values, which counts as a new one.
label.all, line.all, box.all, polyline.all, linefill.all and table.all are arrays of the live drawings of each kind, oldest first. Use them to clear what a script drew:
pine
//@version=6
indicator("Keep the last three crosses", overlay = true)
fast = ta.ema(close, 9)
slow = ta.ema(close, 21)
if ta.cross(fast, slow)
label.new(bar_index, high, fast > slow ? "Up" : "Down")
// label.all holds the oldest first: delete until three are left
while array.size(label.all) > 3
label.delete(array.first(label.all))Polylines and linefills
polyline.new(points) draws one shape through an array of chart.points, straight or with curved = true, and closed = true joins the last point to the first. Build the array, then make the polyline once, since a polyline has no setters.
linefill.new(line1, line2, color) colours the space between two lines and follows them as they move.
pine
//@version=6
indicator("Last five swing highs", overlay = true)
float ph = ta.pivothigh(high, 3, 3)
var array<chart.point> swings = array.new<chart.point>()
var polyline path = na
if not na(ph)
// the swing was 3 bars back, so the point goes there (see Traps)
swings.push(chart.point.from_index(bar_index - 3, ph))
if swings.size() > 5
swings.shift()
if barstate.islast and swings.size() > 1
path.delete() // each live bar is the last: drop the one before
path := polyline.new(swings, line_color = color.orange,
line_width = 2)Tables
A table is a grid fixed to one of nine places on the pane, such as position.top_right, that stays put while the chart scrolls. Make it once with table.new(position, columns, rows) in a var, then fill its cells with table.cell(id, column, row, text).
pine
//@version=6
indicator("Stats table", overlay = true)
var table stats = table.new(position.top_right, 2, 2,
bgcolor = color.new(color.gray, 85))
// work values out on every bar, outside the branch
float rsi = ta.rsi(close, 14)
float atr = ta.atr(14)
if barstate.islast
stats.cell(0, 0, "RSI")
stats.cell(1, 0, str.tostring(rsi, "#.0"))
stats.cell(0, 1, "ATR")
// text_color: without it a cell's text is color.black
stats.cell(1, 1, str.tostring(atr, format.mintick),
text_color = color.orange)table.cellsets the whole cell. Anything the call leaves out goes back to its default, so a secondtable.cellon a cell withoutbgcolorclears the colour the first one set. The one thing kept is a merge made withtable.merge_cells.table.cell_set_text,table.cell_set_bgcolorand the othertable.cell_set_*calls change one property and keep the rest.- A cell's text is
color.black(#363A45) until a call gives it a colour, so on a dark chart givetext_colorevery time. table.merge_cells(id, start_column, start_row, end_column, end_row)joins a block of cells into one, andtable.clearempties a block.
Drawing on the last bar only
A drawing that shows the present only needs work on the last bar. Gate it with if barstate.islast, as the examples above do, and the script makes and updates it once instead of on every bar of history.
A script that does heavy work only to draw it, such as refitting a model and redrawing its lines, can wait for the last closed bar:
pine
//@version=6
indicator("Fit on closed bars only", overlay = true)
var line fit = na
float end = ta.linreg(close, 50, 0)
float slope = end - ta.linreg(close, 50, 1)
// last_bar_index - 1 is the last closed bar while the newest one forms
if barstate.isconfirmed and bar_index >= last_bar_index - 1
fit.delete()
// it starts 49 bars back, so the engine warns (see Traps)
fit := line.new(bar_index - 49, end - 49 * slope, bar_index, end,
color = color.blue)That runs the work once on history, and again each time a live bar closes. It's plain Pine, so it works the same on TradingView. It suits only drawings that are redrawn whole: anything the script keeps from older bars, such as a label at each pivot, would be lost.
Traps
- Drawings vanish from the left. A script that makes a drawing on every bar keeps only the newest 50 of that kind. Raise
max_*_count, or keep one drawing in avarand move it. - Reading drawings into a plot. A plot of
label.all.size(), or of a getter, for drawings made underbarstate.islastshows a history that no live bar had, since those drawings didn't exist then. The engine warns of it as a leak. - A drawing placed on an earlier bar. A label at
bar_index - 5, as a pivot's is, sits on the chart where the pivot was, though the script only knew of it five bars later. Read history with that in mind. The engine warns of it as a leak. ta.*inside the drawing'sif. Ata.*call underif barstate.islastruns on that one bar, so its window is nearly empty. Work values out before the branch, as the table example does. See state in branches.- A deleted drawing reads
na. Its getters givena, and a setter on it changes nothing. Make a new one if you need it back. - Drawings inside a request. A drawing made in the expression of
request.security()does nothing there. Draw on the chart's own bars with the values the request returns.
See also
- Plots and colours, for values that belong to each bar, and for colours
- label.new, line.new, table.cell in the reference
- chart.point
- When a script runs, for
barstate.islastand the forming bar