Skip to content
ChartDeck ChartScript docs Account Open the terminal

Plots

Draw values on the chart with plot(): styles, line widths and dashes, per-bar colors, offsets, where values show, named styles and the 128-plot cap.

A plot is the most common thing a script draws: one value per bar, joined into a line, a histogram, a row of dots or a shaded area. Moving averages, oscillators, bands and volume bars are all plots.

You make one with plot. It takes the value to draw (a series), and optionally a title, a color and a set of style arguments. It returns a plot handle you can pass to fill to shade the space between two plots (see Fills).

#A first plot

ChartScriptOpen in editor
//@chartscript 1
indicator("Fast and slow averages", { overlay: true })
const fastLen = input.int(9, "Fast length", { minval: 1 })
const slowLen = input.int(21, "Slow length", { minval: 1 })

const fast = ta.ema(close, fastLen)
const slow = ta.ema(close, slowLen)

plot(fast, "Fast", color.accent, 2)
plot(slow, "Slow", color.muted, 1)

The first three arguments can be given by position: the series, the title and the color. The fourth is linewidth. Everything after that is clearer by name, in an options object as the last argument: { style: plot.style_columns }.

The title matters more than it looks. It is the name the person using your script sees in the legend, the data window and the Style tab of the settings, so give every plot a short, distinct title.

#The arguments

ArgumentWhat it doesDefault
seriesThe value to draw on each bar. na draws nothing on that bar.required
titleThe name in the legend, data window and Style tab.""
colorA constant color, or a different color on each bar.the theme color
linewidthLine width in pixels. For plot.style_histogram it is the bar width.1
styleHow the values are drawn: one of the plot.style_* constants below.plot.style_line
baseplot.style_range only: the other end of the bar on each bar.none
color_belowplot.style_baseline only: the color below histbase.color.down
linestyleSolid, dashed or dotted line (plot.linestyle_*, dash.* or dash.custom(...)).solid
offsetShift the plot this many bars right (negative: left).0
histbaseThe base level that histograms, columns and areas are drawn from.0
displayWhere the value shows: pane, legend, price scale, data window (display.*).display.all
format, precisionHow the value is written in the legend and on the scale.the script's
show_lastDraw only the last N bars.all bars
autoscalefalse: draw the plot, but leave it out of the price scale's Auto range (see below).true
look, variantGive the plot a named style made with style.plot (see below).none

editable, join and force_overlay are accepted so that code written for other charting script languages compiles, but they change nothing: every plot is always editable in the Style tab, and a plot always draws in its script's pane. trackprice: true draws a dotted horizontal line at the plot's last value across the whole pane, in the plot's color, and puts that value in a tag of the same color on the price scale.

#Plot styles

The style argument picks the shape of the plot.

ConstantDraws
plot.style_lineA continuous line. Bars where the value is na are bridged.
plot.style_linebrA line that breaks where the value is na.
plot.style_steplineHorizontal steps from value to value.
plot.style_steplinebrSteps that break at na.
plot.style_stepline_diamondSteps with a small diamond on each value.
plot.style_areaA line with the area down to histbase lightly shaded.
plot.style_areabrAn area that breaks at na.
plot.style_histogramThin bars from histbase to the value; linewidth sets their width.
plot.style_columnsWide bars from histbase to the value (about 70% of the bar spacing).
plot.style_circlesA dot at each value, nothing in between.
plot.style_crossA small cross at each value.
plot.style_baselineAn area around histbase in two colors: color above it, color_below below it.
plot.style_smoothA smooth curve through the values. Bars where the value is na are bridged.
plot.style_smoothbrA smooth curve that breaks where the value is na.
plot.style_markersA line with a dot on every value.
plot.style_lollipopA thin stem from histbase to the value, with a dot at the value.
plot.style_rangeA floating bar from the value to base on each bar.

This script draws five of them in one pane, each on its own band, so you can compare them:

ChartScriptOpen in editor
//@chartscript 1
indicator("Plot styles", { overlay: false })
// one oscillator, drawn five ways on separate bands of the pane
const osc = ta.rsi(close, 14) - 50

plot(osc + 200, "Line", color.accent, 2)
plot(osc + 100, "Step", color.orange, 1, plot.style_stepline)
plot(osc / 2, "Columns", osc >= 0 ? color.up : color.down, { style: plot.style_columns })
plot(osc - 100, "Histogram", color.muted, 2, plot.style_histogram, { histbase: -100 })
plot(bar_index % 3 == 0 ? osc - 200 : na, "Circles", color.purple, 2, plot.style_circles)
An oscillator pane showing the same values drawn as a line, a step line, colored columns, a histogram and a row of circlesAn oscillator pane showing the same values drawn as a line, a step line, colored columns, a histogram and a row of circles

#Baseline, smooth, markers, lollipop and range

Five styles show a series in ways a plain line cannot. This script draws the same oscillator with each of them:

ChartScriptOpen in editor
//@chartscript 1
indicator("More plot styles", { overlay: false })
// one oscillator, drawn five more ways on separate bands of the pane
const osc = ta.rsi(close, 14) - 50

// two colours around a base level: green above 200, red below it
plot(osc + 200, "Baseline", color.up, 2, { style: plot.style_baseline, histbase: 200, color_below: color.down })
// a value on every fourth bar only: the curve bridges the bars in between
plot(bar_index % 4 == 0 ? osc + 100 : na, "Smooth", color.accent, 2, plot.style_smooth)
plot(osc, "Markers", color.orange, 1, plot.style_markers)
plot(osc - 100, "Lollipop", osc >= 0 ? color.up : color.down, { style: plot.style_lollipop, histbase: -100 })
// a bar from the highest to the lowest value of the last five bars
plot(ta.highest(osc, 5) - 200, "Range", color.purple, { style: plot.style_range, base: ta.lowest(osc, 5) - 200 })
An oscillator pane showing a two-color baseline area, a smooth curve, a line with markers, lollipops and floating range bars, each on its own bandAn oscillator pane showing a two-color baseline area, a smooth curve, a line with markers, lollipops and floating range bars, each on its own band
  • plot.style_baseline fills the space between the line and histbase and colors each side on its own: color where the value is above the base, color_below where it is below (when you leave color_below out it is color.down). Where the line crosses the base, the line and the fill change color exactly at the crossing. Use it for anything that swings around a level: a spread, net flow, an oscillator around zero.
  • plot.style_smooth joins the values with a curve instead of straight pieces. The curve passes through every value and never rises above or falls below the two values it joins, so it does not invent a high or a low. It suits series with a value only now and then (a higher-timeframe value, pivots) and slow averages. plot.style_smoothbr breaks at na like plot.style_linebr.
  • plot.style_markers is plot.style_line with a dot on each value, which shows where the real data points are. When you zoom far out the dots are left out and the line remains.
  • plot.style_lollipop draws a thin stem from histbase to each value with a dot on top. It reads like a histogram but stays light when many bars are on screen, and each bar can have its own color.
  • plot.style_range draws a bar between two series on every bar: the plot's own series and the one you give as base. It is the style for a high and a low, a band, or a forecast range. Without base the bar starts at histbase.

base and color_below are arguments of plot, given by name like the other options. The other styles ignore them.

In the Style tab a member can also switch any plot to baseline area, smooth line, line with markers or lollipop (and back). Range bars are not in that list, because they need the second series from the script.

#Breaks and gaps

A value of na means "nothing on this bar". What happens next depends on the style. plot.style_line carries on from the last real value to the next one, which suits a series with a few missing bars. The br styles (linebr, steplinebr, areabr) leave a gap instead, which is what you want for a line that should only exist some of the time, like a trailing stop that switches sides:

ChartScriptOpen in editor
//@chartscript 1
indicator("Two-sided stop", { overlay: true })
const len = input.int(20, "Length", { minval: 2 })
const mid = ta.sma(close, len)
const band = ta.atr(len) * 2

// only one of these two has a value on any bar
plot(close > mid ? close - band : na, "Long stop", color.up, 2, plot.style_linebr)
plot(close <= mid ? close + band : na, "Short stop", color.down, 2, plot.style_linebr)

#Color on every bar

color can change from bar to bar. Give it any expression that produces a color, typically a conditional:

ChartScriptFragmentOpen in editor
const hist = ta.ema(close, 12) - ta.ema(close, 26)
const grow = hist > nz(hist[1])
plot(
    hist,
    "Momentum",
    hist >= 0 ? (grow ? color.up : color.new(color.up, 50)) : grow ? color.new(color.down, 50) : color.down,
    { style: plot.style_columns }
)

A color that is na on a bar hides the plot on that bar. A constant color: na hides the plot everywhere, which is occasionally useful for a plot that only exists to anchor a fill.

#Line styles and dashes

linestyle draws a line dashed or dotted. You can use the three classic constants or the dash.* set, which has a few more patterns and lets you design your own:

ValuePattern
plot.linestyle_solid, dash.solidSolid
plot.linestyle_dashed, dash.dashedDashes
plot.linestyle_dotted, dash.dottedDots
dash.dashdotDash, dot
dash.longLong dashes
dash.custom(6, 3, 1, 3)Your own: dash, gap, dash, gap ... in pixels (up to 8 segments, each 0.5 to 32 px)
ChartScriptOpen in editor
//@chartscript 1
indicator("Dashed guide lines", { overlay: true })
const mid = ta.sma(close, 50)
plot(mid, "Basis", color.accent, 2)
plot(mid * 1.02, "Upper guide", color.muted, 1, { linestyle: plot.linestyle_dashed })
plot(mid * 0.98, "Lower guide", color.muted, 1, { linestyle: dash.custom(6, 3, 1, 3) })

#Offset

offset shifts the whole plot sideways by a number of bars. A positive offset moves it right, into the future; ChartDeck draws the part past the last bar in the empty space on the right of the chart. A negative offset moves it left.

ChartScriptOpen in editor
//@chartscript 1
indicator("Displaced average", { overlay: true })
const shift = input.int(10, "Shift (bars)", { minval: -100, maxval: 100 })
plot(ta.sma(close, 20), "Displaced SMA", color.orange, 2, { offset: shift })

offset must be known before the script runs: a number, an input, or simple arithmetic on them (+ - * and int()). Changing an offset input does not re-run the script; the chart simply redraws.

#Where the value shows

Every plot can appear in four places: drawn in the pane, as a number in the status line (the legend at the top of the pane), as a label on the price scale, and in the data window. display picks which:

ConstantShows the plot in
display.allEverywhere (the default)
display.noneNowhere; the plot still exists and can be filled or referenced
display.paneThe pane only
display.status_lineThe legend only
display.price_scaleThe price scale label only
display.data_windowThe data window only

You can combine them with + and -. For example, display.all - display.status_line keeps a busy helper line out of the legend, and display.pane + display.data_window keeps it off the price scale:

ChartScriptFragmentOpen in editor
plot(ta.sma(close, 200), "Long-term average", color.muted, 1, { display: display.all - display.status_line })

display can also come from an input, such as show ? display.all : display.none, or display.all - (hideTag ? display.price_scale : display.none). The person using your script then turns the plot on and off without the script running again. Each copy of the indicator follows its own settings: the line, its fill, background and bar colours, its legend value and its price-scale tag. A plot whose display an input chooses always counts toward the pane's automatic range, even while it is hidden.

The input has to reach display directly, through a variable set once, or through ?:, &&, ||, !, comparisons and + / - of display.* values. A function call, a switch or a variable that is reassigned cannot follow the settings: the compiler stops at that line with CS2712 or CS2104, so a display is never fixed at its default without a word.

#Number format and precision

format and precision decide how the plot's value is written in the legend, data window and price scale.

formatShows
format.priceA price, with precision decimals
format.percentA percentage
format.volumeA large number shortened (for example 1.2M)
format.mintickTreated like format.price
format.inheritWhatever the script declared in indicator()
ChartScriptFragmentOpen in editor
plot(ta.roc(close, 10), "Rate of change", color.accent, { format: format.percent, precision: 2 })

#Show only the last bars

show_last: N draws only the last N bars of a plot, handy for a level that only matters near the current price. The person using the script can change it in the Style tab.

ChartScriptFragmentOpen in editor
plot(ta.highest(high, 50), "Recent high", color.up, 1, plot.style_stepline, { show_last: 30 })

#Keep a plot out of the price scale

On the price chart, Auto fits the price scale to the candles only, so the scale is the same with or without your script: no plot, line, box or label widens it. Where a plot or drawing runs past the top or bottom of the pane it is cut off, and it keeps a tag with an arrow at that edge of the price scale (hover it for the name and price). A person can opt one indicator back in with Price scale: Always fit on the Scale tab of its settings; then every plot of it counts, except a plot written with autoscale: false, which stays out of the scale even then.

ChartScriptFragmentOpen in editor
const monthly = ta.vwap(hlc3, timeframe.change("1M"))
plot(monthly, "Monthly VWAP", color.muted, 2, { autoscale: false })
plot(ta.ema(close, 21), "EMA 21", color.accent)

autoscale takes true or false written in the code, not an input.

#Inputs as style

Most style arguments may be inputs: color, linewidth, linestyle and display. They are applied on the chart, so when someone changes such an input the plot is restyled at once and the script does not run again. style may be an input too (directly, or through a function of inputs); the chart then draws the plot in the style of its last value. input.linestyle gives a dash picker:

ChartScriptOpen in editor
//@chartscript 1
indicator("Styled average", { overlay: true })
const len = input.int(34, "Length", { minval: 1 })
const col = input.color(color.orange, "Colour")
const width = input.int(2, "Width", { minval: 1, maxval: 4 })
const dashes = input.linestyle(dash.solid, "Line style")
plot(ta.ema(close, len), "EMA", col, width, { linestyle: dashes })

Whatever you write, every plot also appears in the Style tab, where the user can override its color, opacity, width and dash.

#Named styles: look and variant

When several outputs should share one look, describe it once with style.plot and pass it as look =. The Style tab then shows one entry for the whole group, so the user restyles all of them in one place. style.variant adds named variations of a look, such as a bullish and a bearish color; each output picks one with variant =.

ChartScriptOpen in editor
//@chartscript 1
indicator("Trend averages", { overlay: true })
const trend = style.plot("Trend lines", { color: color.accent, width: 2 })
style.variant(trend, "bull", { color: color.up })
style.variant(trend, "bear", { color: color.down })

plot(ta.ema(close, 20), "EMA 20", { look: trend, variant: "bull" })
plot(ta.ema(close, 50), "EMA 50", { look: trend, variant: "bear" })
plot(ta.ema(close, 100), "EMA 100", { look: trend, linewidth: 1 })

An argument written next to look = wins over the style, so the third line above is thinner but keeps the shared color. style.plot and style.line suit plots and hlines; giving a plot a style of another kind is error CS2725.

#Candles, bars and arrows

Three more plot functions draw price-like data in the script's pane:

  • plotcandle draws candles from four series (open, high, low, close), with color, wickcolor and bordercolor, each of which may change per bar.
  • plotbar draws OHLC bars from the same four series.
  • plotarrow draws an up arrow for a positive value and a down arrow for a negative one, longer for bigger values (between minheight and maxheight pixels), colored colorup / colordown.

This script draws Heikin Ashi candles in their own pane:

ChartScriptOpen in editor
//@chartscript 1
indicator("Heikin Ashi pane", { overlay: false })
const haClose = (open + high + low + close) / 4
state haOpen: float = na
haOpen = na(haOpen[1]) ? (open + close) / 2 : (haOpen[1] + haClose[1]) / 2
const haHigh = math.max(high, math.max(haOpen, haClose))
const haLow = math.min(low, math.min(haOpen, haClose))
plotcandle(
    haOpen,
    haHigh,
    haLow,
    haClose,
    "Heikin Ashi",
    haClose >= haOpen ? color.up : color.down,
    { wickcolor: color.muted }
)

For markers and characters on signal bars, see Text and shapes.

#How many plots

A script may have up to 128 plots. Every call of plot, plotshape, plotchar, plotarrow, plotcandle and plotbar counts as one. Levels (hline), fills, background colors and bar colors do not count. Going over the cap is error CS3001.

Plot calls must be at the top level of the script, not inside an if, a loop or a function: the chart needs to know every plot before the script runs, and a plot in a branch is error CS3007. To show a plot only some of the time, give it na on the bars where it should be hidden, or use a display input.

ChartScriptError: CS3007
//@chartscript 1
indicator("Inside a branch")
if (close > open) {
    plot(close)
}

See the limits page for every cap in one place.

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