0
# Projection Utilities
1
2
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.
3
4
## Core Types
5
6
```typescript { .api }
7
/**
8
* Function that projects input from one domain to another
9
* @param input - The input value to project
10
* @param from - Source domain as [min, max] tuple
11
* @param to - Target domain as [min, max] tuple
12
* @returns Projected value in target domain
13
*/
14
type ProjectorFunction<F, T> = (
15
input: F,
16
from: readonly [F, F],
17
to: readonly [T, T]
18
) => T;
19
20
/**
21
* Function type returned by projection creators
22
* @param input - Reactive input value to project
23
* @returns ComputedRef containing projected value
24
*/
25
type UseProjection<F, T> = (input: MaybeRefOrGetter<F>) => ComputedRef<T>;
26
```
27
28
## Capabilities
29
30
### createProjection
31
32
Creates a reusable numeric projection function for mapping values between number ranges with an optional custom projector.
33
34
```typescript { .api }
35
/**
36
* Creates a numeric projection function for mapping between ranges
37
* @param fromDomain - Source range as [min, max] (reactive)
38
* @param toDomain - Target range as [min, max] (reactive)
39
* @param projector - Optional custom projection function
40
* @returns Projection function that can be called with input values
41
*/
42
function createProjection(
43
fromDomain: MaybeRefOrGetter<readonly [number, number]>,
44
toDomain: MaybeRefOrGetter<readonly [number, number]>,
45
projector?: ProjectorFunction<number, number>
46
): UseProjection<number, number>;
47
```
48
49
**Usage Examples:**
50
51
```typescript
52
import { ref } from "vue";
53
import { createProjection } from "@vueuse/math";
54
55
// Create a projection from 0-100 to 0-1
56
const percentToDecimal = createProjection([0, 100], [0, 1]);
57
58
const percentage = ref(75);
59
const decimal = percentToDecimal(percentage);
60
61
console.log(decimal.value); // 0.75
62
63
percentage.value = 50;
64
console.log(decimal.value); // 0.5
65
66
// Create projection with reactive domains
67
const minInput = ref(0);
68
const maxInput = ref(1024);
69
const minOutput = ref(0);
70
const maxOutput = ref(255);
71
72
const inputRange = computed(() => [minInput.value, maxInput.value] as const);
73
const outputRange = computed(() => [minOutput.value, maxOutput.value] as const);
74
75
const scaleDown = createProjection(inputRange, outputRange);
76
77
const highResValue = ref(512);
78
const lowResValue = scaleDown(highResValue);
79
80
console.log(lowResValue.value); // 127.5
81
82
// Change the output range
83
maxOutput.value = 100;
84
console.log(lowResValue.value); // ~50 (automatically recalculated)
85
86
// Temperature conversion (Celsius to Fahrenheit)
87
const celsiusToFahrenheit = createProjection(
88
[0, 100], // Celsius range (water freezing to boiling)
89
[32, 212], // Fahrenheit range
90
);
91
92
const celsius = ref(25);
93
const fahrenheit = celsiusToFahrenheit(celsius);
94
console.log(fahrenheit.value); // 77°F
95
```
96
97
### createGenericProjection
98
99
Creates a generic projection function that can work with any types, not just numbers, using a custom projector function.
100
101
```typescript { .api }
102
/**
103
* Creates a generic typed projection function for any domain types
104
* @param fromDomain - Source range as [min, max] (reactive)
105
* @param toDomain - Target range as [min, max] (reactive)
106
* @param projector - Custom projection function for the specific types
107
* @returns Generic projection function
108
*/
109
function createGenericProjection<F = number, T = number>(
110
fromDomain: MaybeRefOrGetter<readonly [F, F]>,
111
toDomain: MaybeRefOrGetter<readonly [T, T]>,
112
projector: ProjectorFunction<F, T>
113
): UseProjection<F, T>;
114
```
115
116
**Usage Examples:**
117
118
```typescript
119
import { ref } from "vue";
120
import { createGenericProjection } from "@vueuse/math";
121
122
// Color interpolation between RGB values
123
type RGB = { r: number; g: number; b: number };
124
125
const colorProjector = (
126
input: number,
127
from: readonly [number, number],
128
to: readonly [RGB, RGB]
129
): RGB => {
130
const progress = (input - from[0]) / (from[1] - from[0]);
131
const [startColor, endColor] = to;
132
133
return {
134
r: Math.round(startColor.r + (endColor.r - startColor.r) * progress),
135
g: Math.round(startColor.g + (endColor.g - startColor.g) * progress),
136
b: Math.round(startColor.b + (endColor.b - startColor.b) * progress),
137
};
138
};
139
140
const interpolateColor = createGenericProjection(
141
[0, 100],
142
[{ r: 255, g: 0, b: 0 }, { r: 0, g: 255, b: 0 }], // Red to Green
143
colorProjector
144
);
145
146
const progress = ref(50);
147
const currentColor = interpolateColor(progress);
148
149
console.log(currentColor.value); // { r: 128, g: 128, b: 0 } (yellow)
150
151
// String interpolation example
152
const stringProjector = (
153
input: number,
154
from: readonly [number, number],
155
to: readonly [string, string]
156
): string => {
157
const progress = (input - from[0]) / (from[1] - from[0]);
158
const [start, end] = to;
159
const index = Math.round(progress * (end.length - start.length)) + start.length;
160
161
return start + end.slice(start.length, index);
162
};
163
164
const expandString = createGenericProjection(
165
[0, 10],
166
["Hello", "Hello World!"],
167
stringProjector
168
);
169
170
const step = ref(5);
171
const message = expandString(step);
172
console.log(message.value); // "Hello Wor"
173
```
174
175
### useProjection
176
177
Direct numeric projection function that immediately applies projection to a value without creating a reusable projector.
178
179
```typescript { .api }
180
/**
181
* Directly project a value between numeric ranges
182
* @param input - The value to project (reactive)
183
* @param fromDomain - Source range as [min, max] (reactive)
184
* @param toDomain - Target range as [min, max] (reactive)
185
* @param projector - Optional custom projection function
186
* @returns ComputedRef containing the projected value
187
*/
188
function useProjection(
189
input: MaybeRefOrGetter<number>,
190
fromDomain: MaybeRefOrGetter<readonly [number, number]>,
191
toDomain: MaybeRefOrGetter<readonly [number, number]>,
192
projector?: ProjectorFunction<number, number>
193
): ComputedRef<number>;
194
```
195
196
**Usage Examples:**
197
198
```typescript
199
import { ref } from "vue";
200
import { useProjection } from "@vueuse/math";
201
202
// Direct projection usage
203
const temperature = ref(20); // Celsius
204
const fahrenheit = useProjection(
205
temperature,
206
[0, 100], // Celsius range
207
[32, 212] // Fahrenheit range
208
);
209
210
console.log(fahrenheit.value); // 68°F
211
212
// Slider to progress bar
213
const sliderValue = ref(750);
214
const progressPercent = useProjection(
215
sliderValue,
216
[0, 1000], // Slider range
217
[0, 100] // Percentage range
218
);
219
220
console.log(progressPercent.value); // 75%
221
222
// Reactive domains
223
const minTemp = ref(-10);
224
const maxTemp = ref(40);
225
const currentTemp = ref(20);
226
227
const comfort = useProjection(
228
currentTemp,
229
computed(() => [minTemp.value, maxTemp.value]),
230
[0, 100] // Comfort index 0-100
231
);
232
233
console.log(comfort.value); // 60 (comfort index)
234
235
// Custom projector for non-linear scaling
236
const exponentialProjector = (
237
input: number,
238
from: readonly [number, number],
239
to: readonly [number, number]
240
): number => {
241
const normalizedInput = (input - from[0]) / (from[1] - from[0]);
242
const exponential = Math.pow(normalizedInput, 2); // Square for exponential curve
243
return to[0] + (to[1] - to[0]) * exponential;
244
};
245
246
const linearInput = ref(50);
247
const exponentialOutput = useProjection(
248
linearInput,
249
[0, 100],
250
[0, 1000],
251
exponentialProjector
252
);
253
254
console.log(exponentialOutput.value); // 250 (0.5² * 1000)
255
```
256
257
## Advanced Usage Patterns
258
259
### Animation and Easing
260
261
```typescript
262
import { ref, computed } from "vue";
263
import { createProjection, useProjection } from "@vueuse/math";
264
265
// Create easing projection
266
const easeInOut = (
267
input: number,
268
from: readonly [number, number],
269
to: readonly [number, number]
270
): number => {
271
const t = (input - from[0]) / (from[1] - from[0]);
272
const eased = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
273
return to[0] + (to[1] - to[0]) * eased;
274
};
275
276
const animationProgress = ref(0); // 0 to 1
277
const easedPosition = useProjection(
278
animationProgress,
279
[0, 1],
280
[0, 500], // 500px movement
281
easeInOut
282
);
283
284
// Animate
285
function animate() {
286
animationProgress.value += 0.01;
287
console.log(`Position: ${easedPosition.value}px`);
288
289
if (animationProgress.value < 1) {
290
requestAnimationFrame(animate);
291
}
292
}
293
```
294
295
### Data Visualization Scaling
296
297
```typescript
298
import { ref, computed } from "vue";
299
import { createProjection } from "@vueuse/math";
300
301
// Chart scaling
302
const data = ref([10, 25, 15, 30, 20, 35, 40]);
303
const chartHeight = ref(400);
304
const chartPadding = ref(20);
305
306
// Find data range
307
const dataMin = computed(() => Math.min(...data.value));
308
const dataMax = computed(() => Math.max(...data.value));
309
310
// Create Y-axis projection
311
const dataToPixels = createProjection(
312
computed(() => [dataMin.value, dataMax.value]),
313
computed(() => [chartHeight.value - chartPadding.value, chartPadding.value])
314
);
315
316
// Convert data points to pixel positions
317
const pixelPositions = computed(() =>
318
data.value.map(value => dataToPixels(ref(value)).value)
319
);
320
321
console.log(pixelPositions.value);
322
// [380, 220, 300, 140, 260, 100, 20] (inverted Y for SVG/Canvas)
323
324
// X-axis projection for spacing
325
const indexToPixels = createProjection(
326
[0, data.value.length - 1],
327
[chartPadding.value, 800 - chartPadding.value]
328
);
329
330
const xPositions = computed(() =>
331
data.value.map((_, index) => indexToPixels(ref(index)).value)
332
);
333
```
334
335
### Multi-Range Projections
336
337
```typescript
338
import { ref, computed } from "vue";
339
import { createProjection } from "@vueuse/math";
340
341
// Create different projections for different ranges
342
const input = ref(0);
343
344
// Conditional projection based on input range
345
const complexProjection = computed(() => {
346
const val = input.value;
347
348
if (val <= 50) {
349
// Linear projection for 0-50
350
const linearProj = createProjection([0, 50], [0, 100]);
351
return linearProj(input).value;
352
} else {
353
// Logarithmic projection for 50-100
354
const logProj = createProjection(
355
[50, 100],
356
[100, 1000],
357
(input, from, to) => {
358
const normalized = (input - from[0]) / (from[1] - from[0]);
359
const logValue = Math.log10(1 + normalized * 9); // log10(1 to 10)
360
return to[0] + (to[1] - to[0]) * logValue;
361
}
362
);
363
return logProj(input).value;
364
}
365
});
366
367
// Test different input ranges
368
input.value = 25;
369
console.log(complexProjection.value); // 50 (linear)
370
371
input.value = 75;
372
console.log(complexProjection.value); // ~397 (logarithmic)
373
```