Skip to content
ChartDeck ChartScript docs Account Open the terminal

Tables

Show a grid of text in a corner of the pane with table.new and table.cell: positions, styling, merged cells, updating on the last bar and the caps.

A table is a grid of text cells pinned to a corner of the pane. It does not move with the chart: it stays put while you scroll and zoom. That makes it the natural place for a dashboard: the current readings of several indicators, a summary of the trend on a few lengths, a small statistics panel.

Like labels and lines, a table is a drawing object: you create it from code and fill it with table.cell.

#A dashboard

ChartScriptOpen in editor
//@chartscript 1
indicator("Market dashboard", { overlay: true })
state board: table = table.new(
    position.top_right,
    2,
    5,
    {
        bgcolor: color.new(color.black, 30),
        frame_color: color.muted,
        frame_width: 1,
        border_color: color.new(color.muted, 60),
        border_width: 1
    }
)

const rsi = ta.rsi(close, 14)
const atrPct = ta.atr(14) / close * 100
const volRatio = volume / ta.sma(volume, 20)
const trendUp = close > ta.ema(close, 50)

// fill the table once per update, on the last bar only
if (barstate.islast) {
    board.cell(0, 0, "Reading", { text_color: color.white, text_formatting: text.format_bold })
    board.cell(1, 0, "Value", { text_color: color.white, text_formatting: text.format_bold })
    board.cell(0, 1, "Trend (EMA 50)", { text_color: color.white, text_halign: text.align_left })
    board.cell(1, 1, trendUp ? "Up" : "Down", { text_color: trendUp ? color.up : color.down })
    board.cell(0, 2, "RSI 14", { text_color: color.white, text_halign: text.align_left })
    board.cell(
        1,
        2,
        str.tostring(rsi, "#.0"),
        { text_color: rsi > 70 ? color.down : rsi < 30 ? color.up : color.white }
    )
    board.cell(0, 3, "ATR % of price", { text_color: color.white, text_halign: text.align_left })
    board.cell(1, 3, str.tostring(atrPct, "#.##") + "%", { text_color: color.white })
    board.cell(0, 4, "Volume vs 20-bar avg", { text_color: color.white, text_halign: text.align_left })
    board.cell(1, 4, str.tostring(volRatio, "#.##") + "x", { text_color: volRatio > 2 ? color.orange : color.white })
}
A small two-column table in the top right corner of the chart listing trend, RSI, ATR and volume readingsA small two-column table in the top right corner of the chart listing trend, RSI, ATR and volume readings

Two habits make tables cheap and correct:

  1. Create the table once, with state. Without state, the script would make a new table on every bar.
  2. Fill it on the last bar, inside if (barstate.islast). A table shows only its latest contents, so writing cells on every historical bar is wasted work. On a live chart the last bar runs again with every update, so the table stays current.

#Creating a table

table.new takes a position, a number of columns and a number of rows, then the table's style:

ArgumentWhat it doesDefault
positionThe corner or edge of the pane (see below)required
columns, rowsThe size of the grid. Cells are numbered from 0.required
bgcolorThe background behind all cellsnone
frame_color, frame_widthThe outer bordernone, 0
border_color, border_widthThe lines between cellsnone, 0

The nine positions are position.top_left, position.top_center, position.top_right, position.middle_left, position.middle_center, position.middle_right, position.bottom_left, position.bottom_center and position.bottom_right. Several tables in the same position stack rather than overlap.

#Cells

table.cell sets one cell's text and look. The first three arguments are the table, the column and the row.

ArgumentWhat it doesDefault
textThe cell's text (plain text; \n starts a new line)""
text_colorThe text colorblack
bgcolorThe cell's own backgroundnone
text_halign, text_valigntext.align_left / center / right, text.align_top / center / bottomcenter
text_sizesize.tiny ... size.hugesize.normal
width, heightA share of the pane in %, or 0 for automatic0
tooltipText shown when the pointer hovers over the cellnone
text_font_familyfont.family_default or font.family_monospacedefault
text_formattingtext.format_bold, text.format_italic, or both addednone

The default text color is black, which is hard to read on a dark chart. Always set text_color (and a bgcolor on the table or cells), or use theme colors such as color.text.

Calling table.cell again on the same cell replaces it. To change just one property, use the setters: table.cell_set_text, table.cell_set_bgcolor, table.cell_set_text_color, table.cell_set_tooltip and the rest of the table.cell_set_* family.

Numbers in monospace line up neatly in a column, which helps when a table holds many values:

ChartScriptFragmentOpen in editor
state t: table = table.new(position.bottom_left, 1, 1)
if (barstate.islast) {
    t.cell(
        0,
        0,
        str.tostring(close, format.mintick),
        { text_color: color.text, text_font_family: font.family_monospace }
    )
}

#Merging and clearing

table.merge_cells joins a rectangle of cells into one, with the text of the first. A merged header across the top of a table is the usual use:

ChartScriptOpen in editor
//@chartscript 1
indicator("Returns table", { overlay: true })
state t: table = table.new(
    position.bottom_right,
    3,
    3,
    { bgcolor: color.new(color.black, 30), border_color: color.new(color.muted, 60), border_width: 1 }
)

const pct = (n) => (close - close[n]) / close[n] * 100
const cellColour = (v) => v >= 0 ? color.up : color.down

if (barstate.islast) {
    t.cell(0, 0, "Change over", { text_color: color.white, text_formatting: text.format_bold })
    t.merge_cells(0, 0, 2, 0)
    t.cell(0, 1, "10 bars", { text_color: color.muted })
    t.cell(1, 1, "50 bars", { text_color: color.muted })
    t.cell(2, 1, "200 bars", { text_color: color.muted })
    const r10 = pct(10)
    const r50 = pct(50)
    const r200 = pct(200)
    t.cell(0, 2, str.tostring(r10, "#.##") + "%", { text_color: cellColour(r10) })
    t.cell(1, 2, str.tostring(r50, "#.##") + "%", { text_color: cellColour(r50) })
    t.cell(2, 2, str.tostring(r200, "#.##") + "%", { text_color: cellColour(r200) })
}

table.clear empties a rectangle of cells (or one cell). table.delete removes the whole table.

The table itself has setters too: table.set_position, table.set_bgcolor, table.set_frame_color, table.set_frame_width, table.set_border_color and table.set_border_width. Combined with an input, set_position lets the user move your dashboard:

ChartScriptFragmentOpen in editor
const where = input.string(
    "top_right",
    "Table position",
    { options: ["top_left", "top_right", "bottom_left", "bottom_right"] }
)
state t: table = table.new(position.top_right, 1, 1, { bgcolor: color.new(color.black, 30) })
if (barstate.islast) {
    t.set_position(where)
    t.cell(0, 0, "Hello", { text_color: color.white })
}

#Tables in a panel below the chart

The person using your script can choose, in its Style tab, to show the script's tables in a panel below the chart instead of over it. There the text can be selected and copied, and the panel offers Copy as text and Copy as CSV. You do not need to do anything for this; it works for every table. It is a good reason to keep table text meaningful on its own, with a header row.

#Limits

LimitValue
Tables per script50 (the oldest is removed when a script makes more)
Cells per table2,000

Fifty tables is far more than a readable chart needs. If you find yourself near the limit, one larger table is almost always clearer than many small ones.

The ChartScript documentation is in English for now; translations will follow.