CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-d3-scale

Encodings that map abstract data to visual representation.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

discrete-scales.mddocs/

Discrete Scales

Discrete scales map discrete (categorical) domains to discrete or continuous ranges. They are essential for categorical data visualization including bar charts, scatter plots with categorical axes, discrete color schemes, and any scenario where data values represent distinct categories rather than continuous quantities.

Capabilities

Ordinal Scale

The basic discrete scale that provides direct mapping between discrete domain values and range values.

/**
 * Creates an ordinal scale with optional range
 * @param range - Optional array of output values
 * @returns OrdinalScale instance
 */
function scaleOrdinal(range?: any[]): OrdinalScale;

interface OrdinalScale {
  /** Map domain value to range value */
  (value: any): any;
  
  /** Get or set the discrete input domain */
  domain(): any[];
  domain(domain: any[]): this;
  
  /** Get or set the output range */
  range(): any[];
  range(range: any[]): this;
  
  /** Get or set unknown value handling */
  unknown(): any;
  unknown(value: any): this;
  
  /** Create independent copy */
  copy(): OrdinalScale;
}

Usage Examples:

import { scaleOrdinal, scaleImplicit } from "d3-scale";

// Basic categorical color mapping
const colorScale = scaleOrdinal()
  .domain(['A', 'B', 'C'])
  .range(['red', 'green', 'blue']);

console.log(colorScale('A')); // 'red'
console.log(colorScale('B')); // 'green'
console.log(colorScale('C')); // 'blue'

// Implicit domain (auto-expands as new values encountered)
const implicitScale = scaleOrdinal()
  .range(['#1f77b4', '#ff7f0e', '#2ca02c'])
  .unknown(scaleImplicit);

console.log(implicitScale('category1')); // '#1f77b4' (added to domain)
console.log(implicitScale('category2')); // '#ff7f0e' (added to domain)
console.log(implicitScale.domain()); // ['category1', 'category2']

// Cycling through range when domain is larger
const cyclingScale = scaleOrdinal()
  .domain(['A', 'B', 'C', 'D', 'E'])
  .range(['red', 'blue', 'green']); // Only 3 colors for 5 categories

console.log(cyclingScale('A')); // 'red'
console.log(cyclingScale('D')); // 'red' (cycles back)
console.log(cyclingScale('E')); // 'blue'

// Shape mapping for scatter plots
const shapeScale = scaleOrdinal()
  .domain(['circle', 'square', 'triangle'])
  .range(['●', '■', '▲']);

Band Scale

Creates evenly-spaced bands for categorical data, commonly used for bar charts and categorical axes.

/**
 * Creates a band scale for categorical data with configurable spacing
 * @returns BandScale instance
 */
function scaleBand(): BandScale;

interface BandScale {
  /** Map domain value to band start position */
  (value: any): number | undefined;
  
  /** Get or set the discrete input domain */
  domain(): any[];
  domain(domain: any[]): this;
  
  /** Get or set the continuous output range [start, end] */
  range(): [number, number];
  range(range: [number, number]): this;
  
  /** Get or set range with rounding to integers */
  rangeRound(range: [number, number]): this;
  
  /** Enable or disable rounding */
  round(): boolean;
  round(round: boolean): this;
  
  /** Get or set padding (both inner and outer) */
  padding(): number;
  padding(padding: number): this;
  
  /** Get or set inner padding (between bands) */
  paddingInner(): number;
  paddingInner(padding: number): this;
  
  /** Get or set outer padding (before first and after last band) */
  paddingOuter(): number;
  paddingOuter(padding: number): this;
  
  /** Get or set alignment within available space */
  align(): number;
  align(align: number): this;
  
  /** Get the width of each band */
  bandwidth(): number;
  
  /** Get the distance between starts of adjacent bands */
  step(): number;
  
  /** Create independent copy */
  copy(): BandScale;
}

Usage Examples:

import { scaleBand } from "d3-scale";

// Basic bar chart scale
const xScale = scaleBand()
  .domain(['Q1', 'Q2', 'Q3', 'Q4'])
  .range([0, 400])
  .padding(0.1);

console.log(xScale('Q1'));       // 0 (start of first band)
console.log(xScale('Q2'));       // ~95 (start of second band)
console.log(xScale.bandwidth()); // ~90 (width of each bar)
console.log(xScale.step());      // ~100 (distance between bar starts)

// Centered bars with more padding
const centeredScale = scaleBand()
  .domain(['A', 'B', 'C'])
  .range([50, 350])
  .paddingInner(0.2)
  .paddingOuter(0.1)
  .align(0.5);

// Get band center position
function getBandCenter(scale, value) {
  return scale(value) + scale.bandwidth() / 2;
}

console.log(getBandCenter(centeredScale, 'B')); // Center of band B

// Rounded positions for crisp rendering
const crispScale = scaleBand()
  .domain(['Jan', 'Feb', 'Mar', 'Apr'])
  .rangeRound([0, 400])
  .padding(0.05);

console.log(crispScale('Jan')); // Integer position for crisp rendering

Point Scale

Maps discrete domain to evenly-spaced points, commonly used for scatter plots and line charts with categorical x-axes.

/**
 * Creates a point scale for categorical data positioned at points
 * @returns PointScale instance
 */
function scalePoint(): PointScale;

interface PointScale {
  /** Map domain value to point position */
  (value: any): number | undefined;
  
  /** Get or set the discrete input domain */
  domain(): any[];
  domain(domain: any[]): this;
  
  /** Get or set the continuous output range [start, end] */
  range(): [number, number];
  range(range: [number, number]): this;
  
  /** Get or set range with rounding to integers */
  rangeRound(range: [number, number]): this;
  
  /** Enable or disable rounding */
  round(): boolean;
  round(round: boolean): this;
  
  /** Get or set padding (space before first and after last point) */
  padding(): number;
  padding(padding: number): this;
  
  /** Get or set alignment within available space */
  align(): number;
  align(align: number): this;
  
  /** Get the distance between adjacent points */
  step(): number;
  
  /** Get bandwidth (always 0 for point scales) */
  bandwidth(): number;
  
  /** Create independent copy */
  copy(): PointScale;
}

Usage Examples:

import { scalePoint } from "d3-scale";

// Scatter plot with categorical x-axis
const xScale = scalePoint()
  .domain(['Small', 'Medium', 'Large'])
  .range([50, 350])
  .padding(0.2);

console.log(xScale('Small'));  // ~75 (first point position)
console.log(xScale('Medium')); // ~200 (middle point)
console.log(xScale('Large'));  // ~325 (last point)
console.log(xScale.step());    // ~125 (distance between points)
console.log(xScale.bandwidth()); // 0 (points have no width)

// Line chart with categorical time periods
const timeScale = scalePoint()
  .domain(['Morning', 'Afternoon', 'Evening', 'Night'])
  .range([0, 300]);

// Use for positioning points on line chart
const chartData = [
  { time: 'Morning', value: 20 },
  { time: 'Afternoon', value: 35 },
  { time: 'Evening', value: 25 }
];

chartData.forEach(d => {
  const x = timeScale(d.time);
  console.log(`${d.time}: x=${x}, value=${d.value}`);
});

Quantile Scale

Maps a continuous domain to a discrete range using quantiles computed from sample data.

/**
 * Creates a quantile scale based on sample data distribution
 * @returns QuantileScale instance
 */
function scaleQuantile(): QuantileScale;

interface QuantileScale {
  /** Map domain value to range value based on quantiles */
  (value: number): any;
  
  /** Get or set the sample data used to compute quantiles */
  domain(): number[];
  domain(domain: number[]): this;
  
  /** Get or set the discrete output range */
  range(): any[];
  range(range: any[]): this;
  
  /** Get the computed quantile thresholds */
  quantiles(): number[];
  
  /** Get the domain extent for a given range value */
  invertExtent(value: any): [number, number] | [undefined, undefined];
  
  /** Create independent copy */
  copy(): QuantileScale;
}

Usage Examples:

import { scaleQuantile } from "d3-scale";

// Classify income data into quartiles
const incomeData = [25000, 30000, 35000, 45000, 55000, 65000, 75000, 85000, 95000, 120000];

const incomeScale = scaleQuantile()
  .domain(incomeData)
  .range(['Low', 'Medium-Low', 'Medium-High', 'High']);

console.log(incomeScale.quantiles()); // [37500, 60000, 80000] (quartile boundaries)
console.log(incomeScale(32000));      // 'Low' (below first quartile)
console.log(incomeScale(70000));      // 'Medium-High' (third quartile)

// Color mapping based on data distribution
const colorQuantileScale = scaleQuantile()
  .domain([1, 2, 3, 5, 8, 13, 21, 34, 55, 89]) // Fibonacci sequence
  .range(['#f7fbff', '#deebf7', '#c6dbef', '#9ecae1', '#6baed6']);

console.log(colorQuantileScale(5));  // Color based on quantile position
console.log(colorQuantileScale.invertExtent('#c6dbef')); // [5, 21] - range of values for this color

// Equal representation regardless of data clustering
const testScores = [60, 62, 65, 67, 70, 85, 87, 89, 91, 93, 95, 97, 98, 99, 100];
const gradeScale = scaleQuantile()
  .domain(testScores)
  .range(['D', 'C', 'B', 'A']);

console.log(gradeScale(68)); // Grade based on position in distribution, not absolute value

Quantize Scale

Maps a continuous domain to a discrete range using uniform quantization (equal-sized buckets).

/**
 * Creates a quantize scale with uniform domain subdivision
 * @returns QuantizeScale instance
 */
function scaleQuantize(): QuantizeScale;

interface QuantizeScale {
  /** Map domain value to range value based on uniform buckets */
  (value: number): any;
  
  /** Get or set the continuous input domain [min, max] */
  domain(): [number, number];
  domain(domain: [number, number]): this;
  
  /** Get or set the discrete output range */
  range(): any[];
  range(range: any[]): this;
  
  /** Get the computed threshold values */
  thresholds(): number[];
  
  /** Get the domain extent for a given range value */
  invertExtent(value: any): [number, number] | [undefined, undefined];
  
  /** Create independent copy */
  copy(): QuantizeScale;
}

Usage Examples:

import { scaleQuantize } from "d3-scale";

// Temperature classification  
const tempScale = scaleQuantize()
  .domain([0, 100])  // Celsius range
  .range(['Cold', 'Cool', 'Warm', 'Hot']);

console.log(tempScale.thresholds()); // [25, 50, 75] (equal 25° buckets)
console.log(tempScale(15));          // 'Cold' (0-25 range)
console.log(tempScale(60));          // 'Warm' (50-75 range)
console.log(tempScale.invertExtent('Cool')); // [25, 50]

// Choropleth map classification
const populationScale = scaleQuantize()
  .domain([0, 1000000])  // Population range
  .range(['#f7fbff', '#deebf7', '#c6dbef', '#9ecae1', '#6baed6', '#3182bd']);

console.log(populationScale(150000));  // Color for 150k population
console.log(populationScale.thresholds()); // Equal-sized population buckets

// Performance rating with unequal domain
const performanceScale = scaleQuantize()
  .domain([-2, 3])  // Performance score range
  .range(['Poor', 'Fair', 'Good', 'Excellent']);

console.log(performanceScale(0.5));   // Rating for score 0.5
console.log(performanceScale(-1));    // Rating for negative score

Threshold Scale

Maps a continuous domain to a discrete range using custom threshold values.

/**
 * Creates a threshold scale with custom breakpoints
 * @returns ThresholdScale instance
 */
function scaleThreshold(): ThresholdScale;

interface ThresholdScale {
  /** Map domain value to range value based on thresholds */
  (value: number): any;
  
  /** Get or set the threshold breakpoints */
  domain(): number[];
  domain(domain: number[]): this;
  
  /** Get or set the discrete output range (n+1 values for n thresholds) */
  range(): any[];
  range(range: any[]): this;
  
  /** Get the domain extent for a given range value */
  invertExtent(value: any): [number, number] | [undefined, undefined];
  
  /** Create independent copy */
  copy(): ThresholdScale;
}

Usage Examples:

import { scaleThreshold } from "d3-scale";

// Custom exam grading scale
const gradeScale = scaleThreshold()
  .domain([60, 70, 80, 90])  // Grade thresholds
  .range(['F', 'D', 'C', 'B', 'A']); // 5 grades for 4 thresholds

console.log(gradeScale(55));  // 'F' (below 60)
console.log(gradeScale(75));  // 'C' (70-80 range)
console.log(gradeScale(95));  // 'A' (above 90)
console.log(gradeScale.invertExtent('B')); // [80, 90]

// Air quality index with health categories
const aqiScale = scaleThreshold()
  .domain([51, 101, 151, 201, 301])
  .range(['Good', 'Moderate', 'Unhealthy for Sensitive', 'Unhealthy', 'Very Unhealthy', 'Hazardous']);

console.log(aqiScale(75));   // 'Moderate'
console.log(aqiScale(250));  // 'Very Unhealthy'

// Custom color breaks for choropleth
const choroScale = scaleThreshold()
  .domain([100, 500, 1000, 5000]) // Population density breaks
  .range(['#f7f7f7', '#cccccc', '#969696', '#636363', '#252525']);

console.log(choroScale(750));  // Color for density between 500-1000
console.log(choroScale(50));   // Color for density below 100

Common Patterns

Categorical Color Schemes

Use d3-scale-chromatic for consistent categorical colors:

import { scaleOrdinal } from "d3-scale";
import { 
  schemeCategory10,
  schemeAccent,
  schemeDark2,
  schemePaired,
  schemeSet1,
  schemeSet2,
  schemeSet3
} from "d3-scale-chromatic";

// Standard 10-color categorical palette
const categoryScale = scaleOrdinal(schemeCategory10);

// Paired colors for grouped data
const pairedScale = scaleOrdinal(schemePaired);

// Qualitative color schemes
const accentScale = scaleOrdinal(schemeAccent);

Multi-Level Categorical Data

Handle nested categorical data with combined scales:

import { scaleOrdinal, scaleBand } from "d3-scale";

// Grouped bar chart with categories and subcategories
const categoryScale = scaleBand()
  .domain(['Q1', 'Q2', 'Q3', 'Q4'])
  .range([0, 400])
  .padding(0.1);

const subcategoryScale = scaleBand()
  .domain(['Product A', 'Product B', 'Product C'])
  .range([0, categoryScale.bandwidth()])
  .padding(0.05);

const colorScale = scaleOrdinal()
  .domain(['Product A', 'Product B', 'Product C'])
  .range(['#1f77b4', '#ff7f0e', '#2ca02c']);

// Position calculation for grouped bars
function getBarPosition(category, subcategory) {
  return categoryScale(category) + subcategoryScale(subcategory);
}

console.log(getBarPosition('Q2', 'Product B')); // Position of Q2/Product B bar

Type Definitions

const scaleImplicit: symbol;

Install with Tessl CLI

npx tessl i tessl/npm-d3-scale

docs

continuous-scales.md

discrete-scales.md

diverging-scales.md

index.md

sequential-scales.md

utilities.md

tile.json