Plotting and Styling in Pine Script
Learn how to render values, lines, fills, and shapes on a TradingView chart with Pine Script v5 plotting functions and styling parameters.
This is educational material about programming. Nothing here is investment advice, a trading signal, or a recommendation.
Visualization is where a Pine Script becomes legible. The language ships a small set of drawing functions that turn series of numbers into lines, histograms, bands, and markers. This article covers the core plotting primitives and the styling parameters that control how they render. If you have not set up a script yet, start with Getting Started with Pine Script.
The plot function
plot() is the workhorse. It draws a series — one value per bar — connected as a line by default. Every plotting call must live at the top level of the script (not inside a local block of an if or loop).
//@version=5
indicator("Basic Plot", overlay = true)
emaFast = ta.ema(close, 9)
emaSlow = ta.ema(close, 21)
plot(emaFast, title = "EMA 9", color = color.aqua, linewidth = 2)
plot(emaSlow, title = "EMA 21", color = color.orange, linewidth = 2)overlay = true in the indicator() declaration places plots on the price chart. Without it, plots render in a separate pane below the chart — appropriate for oscillators like RSI.
Choosing a plot style
The style parameter changes the visual form of the same data. Common values:
plot.style_line— the default connected line.plot.style_histogram— vertical bars from zero, useful for momentum.plot.style_columns— filled columns.plot.style_circles/plot.style_cross— discrete markers per bar.plot.style_stepline— holds each value flat until the next bar.plot.style_area— a filled region down to zero.
//@version=5
indicator("MACD Histogram")
[macdLine, signalLine, hist] = ta.macd(close, 12, 26, 9)
plot(hist, style = plot.style_histogram, color = hist >= 0 ? color.green : color.red)Note how color accepts an expression. Evaluated per bar, it lets you color a histogram by sign or a line by trend without extra plots.
Conditional and dynamic color
Color is a series too. You can compute it once and reuse it:
//@version=5
indicator("Dynamic Color", overlay = true)
ma = ta.sma(close, 50)
trendColor = close > ma ? color.new(color.teal, 0) : color.new(color.maroon, 0)
plot(ma, color = trendColor, linewidth = 2)color.new(baseColor, transp) creates a color with a transparency value from 0 (opaque) to 100 (invisible). Use it to soften fills and bands.
Bands and fills with fill
To shade the area between two plots, capture their handles in variables and pass them to fill().
//@version=5
indicator("Band Fill", overlay = true)
basis = ta.sma(close, 20)
dev = 2 * ta.stdev(close, 20)
upper = basis + dev
lower = basis - dev
pUpper = plot(upper, color = color.gray)
pLower = plot(lower, color = color.gray)
fill(pUpper, pLower, color = color.new(color.blue, 90), title = "Band")The high transparency keeps the price action readable through the fill.
Markers with plotshape and plotchar
For discrete annotations — flagging where a condition becomes true — use plotshape() or plotchar() rather than plot(). They render a symbol only on bars where the input is true (or non-na).
//@version=5
indicator("Crossover Markers", overlay = true)
fast = ta.ema(close, 9)
slow = ta.ema(close, 21)
crossUp = ta.crossover(fast, slow)
plotshape(crossUp, style = shape.triangleup, location = location.belowbar, color = color.green, size = size.small)location controls placement (location.abovebar, location.belowbar, location.top, location.absolute). These markers describe what the code computed; they are not instructions to act.
Horizontal references with hline
hline() draws a static horizontal level — handy for oscillator thresholds.
//@version=5
indicator("RSI")
rsi = ta.rsi(close, 14)
plot(rsi, color = color.purple)
hline(70, "Upper", color = color.gray, linestyle = hline.style_dashed)
hline(30, "Lower", color = color.gray, linestyle = hline.style_dashed)hline only accepts a constant numeric value, not a series. For a moving level, use a regular plot.
Keeping plots readable
A few habits make charts easier to maintain:
- Always pass a
titleso plots are identifiable in the Settings and Style tabs. - Limit a script to a handful of plots; dense charts hide the signal.
- Prefer transparency for fills and supporting series so primary data stays prominent.
- Use
display = display.noneto compute a value for alerts without cluttering the chart.
Where to go next
You can now render and style most of what an indicator needs to show. The natural follow-up is wiring those computed conditions to notifications — continue with Creating Alerts in Pine Script to learn how alertcondition and alert() turn plotted logic into events you can subscribe to.