---
title: na and Color
description: "Working with missing values in kScript: the na helpers (isna, nz, fixnan), how NaN behaves, and building colors with color.rgb and color.new."
---

<div class="flex gap-3 mb-6">
  <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-blue-50 text-blue-600 text-sm font-medium">
    Core Concept
  </span>
  <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-gray-100 text-gray-600 text-sm font-medium">
    4 min read
  </span>
</div>

## Why na matters

`na` is kScript's "no value here." You meet it constantly: an indicator hasn't warmed up yet, a `find` matched nothing, a source has no row on this bar. The important rule is that **`na` is contagious in arithmetic**: any expression touching an `na` becomes `na`. Add `5` to a not-yet-warmed SMA and you get `na`, and an `na` value plots as a gap, not a zero. So the skill is detecting `na` and filling it before it poisons a calculation or blanks a line.

These helpers do that:

| Helper | Returns | Use it to |
| --- | --- | --- |
| `isna(x)` | boolean | branch on whether a value is missing |
| `isnan(x)` | boolean | check for NaN specifically (treated as `na`) |
| `isnum(x)` | boolean | the inverse: is this a usable number |
| `nz(x, fallback)` | number | replace a missing `x` with a fallback |
| `fixnan(series)` | series | hold the last good value forward over gaps |

`NaN` and `na` compare equal, so a NaN that sneaks in from bad math is caught by the same `na` checks.

## Filling and forward-holding

`nz(x, fallback)` is the everyday tool: if `x` is `na`, you get `fallback`, otherwise you get `x`. It is how you keep a plot continuous or keep arithmetic finite.

`fixnan(series)` is the series-level cousin: wherever the series is `na`, it substitutes the most recent non-`na` value, so a gappy series becomes a stepped, continuous one. Reach for it when an upstream feed has holes you'd rather hold flat than drop.

```javascript title="scripts/probes/lang-nacolor/na_color_happy.ks"
//@version=2
define(title="na and Color Happy Path", position="offchart", axis=true)

timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
timeseries closeSeries = trade.close
timeseries slow = sma(source=closeSeries, period=20)
timeseries fixed = fixnan(source=slow)

var propagated = slow + 5
var filled = nz(propagated, closeSeries)
var checks = (isna(propagated) ? 1 : 0) + (isnan(NaN) ? 1 : 0) + (isnum(closeSeries) ? 1 : 0) + ((NaN == na) ? 1 : 0)
var baseColor = color.rgb(r=37, g=99, b=235)
var softColor = color.new(color=baseColor, transp=35)

plotLine(value=filled + checks, colors=[baseColor], width=2, label=["nz"], desc=["nz fills na propagated arithmetic"])
plotLine(value=nz(fixed, closeSeries), colors=[softColor], width=2, label=["fixnan"], desc=["fixnan returns a plottable series"])
```


Walking the key lines:

- `var propagated = slow + 5` is deliberately `na` for the first 20 bars: the SMA hasn't warmed up, and adding `5` to `na` stays `na`. That is the contagion in action.
- `nz(propagated, closeSeries)` patches those early bars with the close, so the line is continuous from bar 0 instead of starting blank at bar 20.
- `fixnan(slow)` produces a version of the SMA with no internal gaps.

What you'll see: two continuous lines from the first bar onward, no leading gap, because every potentially-`na` value has a fallback.

## Building colors

Colors are first-class values. Two constructors cover most needs:

- **`color.rgb(r, g, b)`** builds a color from red, green, blue channels, each `0`-`255`.
- **`color.new(color, transp)`** takes an existing color and applies transparency (`0` opaque, `100` fully transparent).

Store the result in a `var` and pass it in a plot's `colors=[...]` array, exactly as the example above does with `baseColor` and a 35%-transparent `softColor`. For the named-constant palette (`red`, `teal`, ...) see [Color Constants](color-constants.md); for the full color API see [Color Functions](../functions/color-functions.md).

## Channel values are validated

Channels must stay in `0`-`255`. An out-of-range channel is caught and fails loudly rather than wrapping or clamping silently:

```javascript title="scripts/probes/lang-nacolor/invalid_color_boundary.ks"
//@version=2
define(title="Invalid Color Boundary", position="offchart", axis=true)

timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
var badColor = color.rgb(r=300, g=0, b=0)

plotLine(value=trade.close, colors=[badColor], width=2, label=["Bad color"], desc=["invalid color boundary"])
```


Here `r=300` triggers `color.rgb.r must be between 0 and 255 at 5:16`. If a color call errors, check that every channel is within `0`-`255`.
