Skip to content

Wavelength and Spectrum Sliders

Picking a color by dragging a slider along a visible-light gradient is a recurring PhET interaction (light/color sims, wave-interference sims, anything with a "choose a wavelength" control). scenerystack/scenery-phet provides this in two layers: generic color-gradient slider parts (SpectrumSliderThumb, SpectrumSliderTrack, built on SpectrumNode), and a ready-made wavelength-specific composite (WavelengthNumberControl, WavelengthSpectrumNode), all backed by VisibleColor's physically-grounded wavelength-to-RGB conversion.

ts
import { WavelengthNumberControl } from 'scenerystack/scenery-phet';
import { NumberProperty } from 'scenerystack/axon';

// A wavelength in nanometers, defaulting to a value in the visible range.
const wavelengthProperty = new NumberProperty( 550 );

const wavelengthControl = new WavelengthNumberControl( wavelengthProperty );

VisibleColor: the wavelength <-> RGB conversion

VisibleColor is a static utility object (not a class) mapping between nanometer wavelengths and sRGB Colors using a precomputed lookup table, plus a couple of physical constants.

MemberValue / signatureNotes
MIN_WAVELENGTH / MAX_WAVELENGTH380 / 780 (nm)The visible range the lookup table covers
MIN_FREQUENCY / MAX_FREQUENCYDerived from SPEED_OF_LIGHTIn Hz
SPEED_OF_LIGHT299792458 (m/s)Speed of light in a vacuum
wavelengthToColor( wavelength, options? )( number, WavelengthToColorOptions? ) => ColorBelow MIN_WAVELENGTH is "UV," above MAX_WAVELENGTH is "IR" — both return options.uvColor/options.irColor (default null, meaning you must supply one to avoid an assertion) rather than a spectrum color
frequencyToColor( frequency, options? )( number, WavelengthToColorOptions? ) => ColorConverts frequency to wavelength first, then calls wavelengthToColor
colorToWavelength( color, reduceIntensityAtExtrema? )( Color, boolean? ) => numberThe inverse lookup; asserts if no close-enough match is found in the table
isVisibleWavelength / isIRWavelength / isUVWavelength( number ) => booleanRange checks against MIN_WAVELENGTH/MAX_WAVELENGTH

reduceIntensityAtExtrema (default true on the options) dims colors near the edges of the visible range, since perceived brightness falls off there — pass false for full-intensity colors throughout.

ts
import { VisibleColor } from 'scenerystack/scenery-phet';

const greenish = VisibleColor.wavelengthToColor( 550 ); // ~green
const isVisible = VisibleColor.isVisibleWavelength( 900 ); // false (infrared)

SpectrumSliderTrack and SpectrumSliderThumb: generic gradient slider parts

These are a matched SliderTrack/(thumb Node) pair — see Slider, SliderTrack, and SliderThumb — meant to be passed as trackNode/thumbNode options to a plain Slider when you want the track itself to be the color gradient, rather than a plain gray bar. Both take the same valueToColor: ( value: number ) => Color function so the track and the thumb's fill stay in sync.

TypeBase classKey options
SpectrumSliderTrackSliderTrack (sun)valueToColor (default grayscale), size (default 150 x 30); internally builds a SpectrumNode for the visual
SpectrumSliderThumbPathvalueToColor, width/height (default 35 x 45) for the teardrop handle shape, cursorHeight/cursorWidth for the thin indicator line overlaid on the track
ts
import { SpectrumSliderTrack, SpectrumSliderThumb, VisibleColor } from 'scenerystack/scenery-phet';
import { Slider } from 'scenerystack/sun';
import { NumberProperty } from 'scenerystack/axon';
import { Range } from 'scenerystack/dot';

const valueProperty = new NumberProperty( 550 );
const range = new Range( VisibleColor.MIN_WAVELENGTH, VisibleColor.MAX_WAVELENGTH );

const trackNode = new SpectrumSliderTrack( valueProperty, range, {
  valueToColor: VisibleColor.wavelengthToColor
} );
const thumbNode = new SpectrumSliderThumb( valueProperty, {
  valueToColor: VisibleColor.wavelengthToColor
} );

const spectrumSlider = new Slider( valueProperty, range, {
  trackNode: trackNode,
  thumbNode: thumbNode
} );

WavelengthSpectrumNode and WavelengthNumberControl: the wavelength-specific composites

WavelengthSpectrumNode is a SpectrumNode subclass that hard-codes valueToColor to VisibleColor.wavelengthToColor and defaults its minValue/maxValue to VisibleColor.MIN_WAVELENGTH/MAX_WAVELENGTH — a static, non-interactive rendering of the visible spectrum bar, useful as a legend or backdrop.

WavelengthNumberControl goes further and assembles the entire interactive control: it's a NumberControl whose slider is built from SpectrumSliderTrack/SpectrumSliderThumb (both wired to VisibleColor.wavelengthToColor by default), plus the standard title and numeric ("### nm") readout.

ts
import { WavelengthNumberControl, WavelengthSpectrumNode } from 'scenerystack/scenery-phet';
import { NumberProperty } from 'scenerystack/axon';
import { Range, Dimension2 } from 'scenerystack/dot';

const wavelengthProperty = new NumberProperty( 650 );

// The full interactive control: title, slider with gradient track, and "650 nm" readout.
const control = new WavelengthNumberControl( wavelengthProperty, {
  range: new Range( 400, 700 ) // narrower than the full visible range, if desired
} );

// A static, non-interactive spectrum bar, e.g. as a legend.
const legend = new WavelengthSpectrumNode( {
  size: new Dimension2( 200, 20 )
} );

Reach for WavelengthNumberControl first

Unless you need a bare gradient bar (WavelengthSpectrumNode) or a custom composite slider (assembled from SpectrumSliderTrack/SpectrumSliderThumb directly), WavelengthNumberControl is the one-line way to get a fully wired wavelength picker with title, gradient slider, and numeric readout, matching the standard PhET look.