CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vueuse--math

Reactive mathematical utility functions and composables for Vue.js applications

Pending
Overview
Eval results
Files

projection.mddocs/

Projection Utilities

Functions for mapping values between different numerical ranges, useful for data visualization, animations, and value transformations. These utilities provide both numeric and generic type-safe projection capabilities.

Core Types

/**
 * Function that projects input from one domain to another
 * @param input - The input value to project
 * @param from - Source domain as [min, max] tuple
 * @param to - Target domain as [min, max] tuple
 * @returns Projected value in target domain
 */
type ProjectorFunction<F, T> = (
  input: F, 
  from: readonly [F, F], 
  to: readonly [T, T]
) => T;

/**
 * Function type returned by projection creators
 * @param input - Reactive input value to project
 * @returns ComputedRef containing projected value
 */
type UseProjection<F, T> = (input: MaybeRefOrGetter<F>) => ComputedRef<T>;

Capabilities

createProjection

Creates a reusable numeric projection function for mapping values between number ranges with an optional custom projector.

/**
 * Creates a numeric projection function for mapping between ranges
 * @param fromDomain - Source range as [min, max] (reactive)
 * @param toDomain - Target range as [min, max] (reactive)
 * @param projector - Optional custom projection function
 * @returns Projection function that can be called with input values
 */
function createProjection(
  fromDomain: MaybeRefOrGetter<readonly [number, number]>,
  toDomain: MaybeRefOrGetter<readonly [number, number]>,
  projector?: ProjectorFunction<number, number>
): UseProjection<number, number>;

Usage Examples:

import { ref } from "vue";
import { createProjection } from "@vueuse/math";

// Create a projection from 0-100 to 0-1
const percentToDecimal = createProjection([0, 100], [0, 1]);

const percentage = ref(75);
const decimal = percentToDecimal(percentage);

console.log(decimal.value); // 0.75

percentage.value = 50;
console.log(decimal.value); // 0.5

// Create projection with reactive domains
const minInput = ref(0);
const maxInput = ref(1024);
const minOutput = ref(0);
const maxOutput = ref(255);

const inputRange = computed(() => [minInput.value, maxInput.value] as const);
const outputRange = computed(() => [minOutput.value, maxOutput.value] as const);

const scaleDown = createProjection(inputRange, outputRange);

const highResValue = ref(512);
const lowResValue = scaleDown(highResValue);

console.log(lowResValue.value); // 127.5

// Change the output range
maxOutput.value = 100;
console.log(lowResValue.value); // ~50 (automatically recalculated)

// Temperature conversion (Celsius to Fahrenheit)
const celsiusToFahrenheit = createProjection(
  [0, 100],   // Celsius range (water freezing to boiling)
  [32, 212],  // Fahrenheit range
);

const celsius = ref(25);
const fahrenheit = celsiusToFahrenheit(celsius);
console.log(fahrenheit.value); // 77°F

createGenericProjection

Creates a generic projection function that can work with any types, not just numbers, using a custom projector function.

/**
 * Creates a generic typed projection function for any domain types
 * @param fromDomain - Source range as [min, max] (reactive)
 * @param toDomain - Target range as [min, max] (reactive)
 * @param projector - Custom projection function for the specific types
 * @returns Generic projection function
 */
function createGenericProjection<F = number, T = number>(
  fromDomain: MaybeRefOrGetter<readonly [F, F]>,
  toDomain: MaybeRefOrGetter<readonly [T, T]>,
  projector: ProjectorFunction<F, T>
): UseProjection<F, T>;

Usage Examples:

import { ref } from "vue";
import { createGenericProjection } from "@vueuse/math";

// Color interpolation between RGB values
type RGB = { r: number; g: number; b: number };

const colorProjector = (
  input: number,
  from: readonly [number, number],
  to: readonly [RGB, RGB]
): RGB => {
  const progress = (input - from[0]) / (from[1] - from[0]);
  const [startColor, endColor] = to;
  
  return {
    r: Math.round(startColor.r + (endColor.r - startColor.r) * progress),
    g: Math.round(startColor.g + (endColor.g - startColor.g) * progress),
    b: Math.round(startColor.b + (endColor.b - startColor.b) * progress),
  };
};

const interpolateColor = createGenericProjection(
  [0, 100],
  [{ r: 255, g: 0, b: 0 }, { r: 0, g: 255, b: 0 }], // Red to Green
  colorProjector
);

const progress = ref(50);
const currentColor = interpolateColor(progress);

console.log(currentColor.value); // { r: 128, g: 128, b: 0 } (yellow)

// String interpolation example
const stringProjector = (
  input: number,
  from: readonly [number, number],
  to: readonly [string, string]
): string => {
  const progress = (input - from[0]) / (from[1] - from[0]);
  const [start, end] = to;
  const index = Math.round(progress * (end.length - start.length)) + start.length;
  
  return start + end.slice(start.length, index);
};

const expandString = createGenericProjection(
  [0, 10],
  ["Hello", "Hello World!"],
  stringProjector
);

const step = ref(5);
const message = expandString(step);
console.log(message.value); // "Hello Wor"

useProjection

Direct numeric projection function that immediately applies projection to a value without creating a reusable projector.

/**
 * Directly project a value between numeric ranges
 * @param input - The value to project (reactive)
 * @param fromDomain - Source range as [min, max] (reactive)
 * @param toDomain - Target range as [min, max] (reactive)
 * @param projector - Optional custom projection function
 * @returns ComputedRef containing the projected value
 */
function useProjection(
  input: MaybeRefOrGetter<number>,
  fromDomain: MaybeRefOrGetter<readonly [number, number]>,
  toDomain: MaybeRefOrGetter<readonly [number, number]>,
  projector?: ProjectorFunction<number, number>
): ComputedRef<number>;

Usage Examples:

import { ref } from "vue";
import { useProjection } from "@vueuse/math";

// Direct projection usage
const temperature = ref(20); // Celsius
const fahrenheit = useProjection(
  temperature,
  [0, 100],   // Celsius range
  [32, 212]   // Fahrenheit range
);

console.log(fahrenheit.value); // 68°F

// Slider to progress bar
const sliderValue = ref(750);
const progressPercent = useProjection(
  sliderValue,
  [0, 1000],  // Slider range
  [0, 100]    // Percentage range
);

console.log(progressPercent.value); // 75%

// Reactive domains
const minTemp = ref(-10);
const maxTemp = ref(40);
const currentTemp = ref(20);

const comfort = useProjection(
  currentTemp,
  computed(() => [minTemp.value, maxTemp.value]),
  [0, 100] // Comfort index 0-100
);

console.log(comfort.value); // 60 (comfort index)

// Custom projector for non-linear scaling
const exponentialProjector = (
  input: number,
  from: readonly [number, number],
  to: readonly [number, number]
): number => {
  const normalizedInput = (input - from[0]) / (from[1] - from[0]);
  const exponential = Math.pow(normalizedInput, 2); // Square for exponential curve
  return to[0] + (to[1] - to[0]) * exponential;
};

const linearInput = ref(50);
const exponentialOutput = useProjection(
  linearInput,
  [0, 100],
  [0, 1000],
  exponentialProjector
);

console.log(exponentialOutput.value); // 250 (0.5² * 1000)

Advanced Usage Patterns

Animation and Easing

import { ref, computed } from "vue";
import { createProjection, useProjection } from "@vueuse/math";

// Create easing projection
const easeInOut = (
  input: number,
  from: readonly [number, number],
  to: readonly [number, number]
): number => {
  const t = (input - from[0]) / (from[1] - from[0]);
  const eased = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
  return to[0] + (to[1] - to[0]) * eased;
};

const animationProgress = ref(0); // 0 to 1
const easedPosition = useProjection(
  animationProgress,
  [0, 1],
  [0, 500], // 500px movement
  easeInOut
);

// Animate
function animate() {
  animationProgress.value += 0.01;
  console.log(`Position: ${easedPosition.value}px`);
  
  if (animationProgress.value < 1) {
    requestAnimationFrame(animate);
  }
}

Data Visualization Scaling

import { ref, computed } from "vue";
import { createProjection } from "@vueuse/math";

// Chart scaling
const data = ref([10, 25, 15, 30, 20, 35, 40]);
const chartHeight = ref(400);
const chartPadding = ref(20);

// Find data range
const dataMin = computed(() => Math.min(...data.value));
const dataMax = computed(() => Math.max(...data.value));

// Create Y-axis projection
const dataToPixels = createProjection(
  computed(() => [dataMin.value, dataMax.value]),
  computed(() => [chartHeight.value - chartPadding.value, chartPadding.value])
);

// Convert data points to pixel positions
const pixelPositions = computed(() => 
  data.value.map(value => dataToPixels(ref(value)).value)
);

console.log(pixelPositions.value);
// [380, 220, 300, 140, 260, 100, 20] (inverted Y for SVG/Canvas)

// X-axis projection for spacing
const indexToPixels = createProjection(
  [0, data.value.length - 1],
  [chartPadding.value, 800 - chartPadding.value]
);

const xPositions = computed(() =>
  data.value.map((_, index) => indexToPixels(ref(index)).value)
);

Multi-Range Projections

import { ref, computed } from "vue";
import { createProjection } from "@vueuse/math";

// Create different projections for different ranges
const input = ref(0);

// Conditional projection based on input range
const complexProjection = computed(() => {
  const val = input.value;
  
  if (val <= 50) {
    // Linear projection for 0-50
    const linearProj = createProjection([0, 50], [0, 100]);
    return linearProj(input).value;
  } else {
    // Logarithmic projection for 50-100
    const logProj = createProjection(
      [50, 100],
      [100, 1000],
      (input, from, to) => {
        const normalized = (input - from[0]) / (from[1] - from[0]);
        const logValue = Math.log10(1 + normalized * 9); // log10(1 to 10)
        return to[0] + (to[1] - to[0]) * logValue;
      }
    );
    return logProj(input).value;
  }
});

// Test different input ranges
input.value = 25;
console.log(complexProjection.value); // 50 (linear)

input.value = 75;
console.log(complexProjection.value); // ~397 (logarithmic)

Install with Tessl CLI

npx tessl i tessl/npm-vueuse--math

docs

aggregation.md

basic-math.md

generic-math.md

index.md

logical.md

projection.md

value-control.md

tile.json