forked from sparrow-vis/sparrow
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(scale): add linear, identity, band, ordinal
Showing
10 changed files
with
252 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { createBand, bandWidth, bandStep } from '../../src/scale'; | ||
|
||
describe('test Band', () => { | ||
const options = { | ||
domain: ['a', 'b', 'c'], | ||
range: [0, 32], | ||
padding: 0.2, | ||
}; | ||
|
||
test('createBand(options) return mapper maps discrete domain to continuous range.', () => { | ||
const s = createBand(options); | ||
expect(s('a')).toBe(2); | ||
expect(s('b')).toBe(12); | ||
expect(s('c')).toBe(22); | ||
}); | ||
|
||
test('bandWidth(options) returns band width.', () => { | ||
expect(bandWidth(options)).toBe(8); | ||
}); | ||
|
||
test('bandStep(options) returns step width.', () => { | ||
expect(bandStep(options)).toBe(10); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { createIdentity } from '../../src/scale'; | ||
|
||
describe('tes Identity', () => { | ||
test('createIdentity() returns a identity function.', () => { | ||
const s = createIdentity(); | ||
|
||
expect(s(1)).toBe(1); | ||
expect(s(true)).toBe(true); | ||
expect(s('hello world')).toBe('hello world'); | ||
expect(s({ a: 1 })).toEqual({ a: 1 }); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { | ||
createLinear, | ||
linearNice, | ||
linearTicks, | ||
interpolateNumber, | ||
} from '../../src/scale'; | ||
|
||
describe('test Linear', () => { | ||
test('createLinear(options) returns a a linear function.', () => { | ||
const s = createLinear({ | ||
domain: [0, 1], | ||
range: [0, 100], | ||
}); | ||
|
||
expect(s(0)).toBe(0); | ||
expect(s(0.3)).toBe(30); | ||
expect(s(0.5)).toBe(50); | ||
expect(s(0.7)).toBe(70); | ||
expect(s(1)).toBe(100); | ||
}); | ||
|
||
test('createLinear(options) uses custom interpolate.', () => { | ||
const s = createLinear({ | ||
domain: [0, 1], | ||
range: ['a', 'z'], | ||
interpolate: (t, start, end) => { | ||
const charCode = interpolateNumber( | ||
t, | ||
start.charCodeAt(), | ||
end.charCodeAt(), | ||
); | ||
return String.fromCharCode(charCode); | ||
}, | ||
}); | ||
|
||
expect(s(0)).toBe('a'); | ||
expect(s(1)).toBe('z'); | ||
expect(s(0.5)).toBe('m'); | ||
}); | ||
|
||
test('linearNice(domain, tickCount) extends domain for better ticks.', () => { | ||
expect(linearNice([1.1, 10.9], 10)).toEqual([1, 11]); | ||
expect(linearNice([0.7, 11.001], 10)).toEqual([0, 12]); | ||
expect(linearNice([0, 0.49], 10)).toEqual([0, 0.5]); | ||
expect(linearNice([12, 87], 5)).toEqual([0, 100]); | ||
expect(linearNice([12, 87], 10)).toEqual([10, 90]); | ||
expect(linearNice([12, 87], 100)).toEqual([12, 87]); | ||
}); | ||
|
||
test('linearTicks(domain, tickCount) return ticks in 1, 2, 5 * 10 ^ n format', () => { | ||
const ticks10 = linearTicks([0, 1], 10); | ||
const ticks9 = linearTicks([0, 1], 9); | ||
const ticks8 = linearTicks([0, 1], 9); | ||
const ticks7 = linearTicks([0, 1], 7); | ||
const ticks6 = linearTicks([0, 1], 6); | ||
const ticks5 = linearTicks([0, 1], 5); | ||
const ticks4 = linearTicks([0, 1], 4); | ||
const ticks3 = linearTicks([0, 1], 3); | ||
const ticks2 = linearTicks([0, 1], 2); | ||
const ticks1 = linearTicks([0, 1], 1); | ||
expect(ticks10).toEqual([ | ||
0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, | ||
]); | ||
expect(ticks9).toEqual([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]); | ||
expect(ticks8).toEqual([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]); | ||
expect(ticks7).toEqual([0, 0.2, 0.4, 0.6, 0.8, 1]); | ||
expect(ticks6).toEqual([0, 0.2, 0.4, 0.6, 0.8, 1]); | ||
expect(ticks5).toEqual([0, 0.2, 0.4, 0.6, 0.8, 1]); | ||
expect(ticks4).toEqual([0, 0.2, 0.4, 0.6, 0.8, 1]); | ||
expect(ticks3).toEqual([0, 0.5, 1]); | ||
expect(ticks2).toEqual([0, 0.5, 1]); | ||
expect(ticks1).toEqual([0, 1]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { createOrdinal } from '../../src/scale'; | ||
|
||
describe('test Ordinal', () => { | ||
test('createOrdinal(options) returns a one-to-one mapper.', () => { | ||
const s = createOrdinal({ | ||
domain: ['a', 'b', 'c'], | ||
range: ['red', 'yellow', 'blue'], | ||
}); | ||
expect(s('a')).toBe('red'); | ||
expect(s('b')).toBe('yellow'); | ||
expect(s('c')).toBe('blue'); | ||
}); | ||
|
||
test('createOrdinal(options) will mod map.', () => { | ||
const s = createOrdinal({ | ||
domain: ['a', 'b', 'c', 'd', 'e'], | ||
range: ['red', 'yellow', 'blue'], | ||
}); | ||
expect(s('d')).toBe('red'); | ||
expect(s('e')).toBe('yellow'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { createOrdinal } from './ordinal'; | ||
|
||
export function createBand(options) { | ||
const { bandRange } = band(options); | ||
return createOrdinal({ ...options, range: bandRange }); | ||
} | ||
|
||
export function bandWidth(options) { | ||
return band(options).bandWidth; | ||
} | ||
|
||
export function bandStep(options) { | ||
return band(options).step; | ||
} | ||
|
||
function band({ domain, range, padding }) { | ||
const [r0, r1] = range; | ||
const n = domain.length; | ||
const step = (r1 - r0) / (n + padding); | ||
const bandWidth = step * (1 - padding); | ||
const interval = step - bandWidth; | ||
return { | ||
step, | ||
bandWidth, | ||
bandRange: new Array(n).fill(0).map((_, i) => r0 + interval + step * i), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export function createIdentity() { | ||
return (x) => x; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export { | ||
createLinear, linearNice, linearTicks, interpolateNumber, | ||
} from './linear'; | ||
export { createIdentity } from './identity'; | ||
export { createOrdinal } from './ordinal'; | ||
export { createBand, bandStep, bandWidth } from './band'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
export function createLinear({ | ||
domain: [d0, d1], | ||
range: [r0, r1], | ||
interpolate = interpolateNumber, | ||
}) { | ||
return (x) => { | ||
const t = normalize(x, d0, d1); | ||
return interpolate(t, r0, r1); | ||
}; | ||
} | ||
|
||
export function linearNice(domain, tickCount) { | ||
const step = tickStep(...domain, tickCount); | ||
return nice(domain, { | ||
floor: (x) => floor(x, step), | ||
ceil: (x) => ceil(x, step), | ||
}); | ||
} | ||
|
||
export function linearTicks(domain, tickCount) { | ||
return ticks(...domain, tickCount); | ||
} | ||
|
||
export function normalize(value, start, stop) { | ||
return (value - start) / (stop - start); | ||
} | ||
|
||
export function interpolateNumber(t, start, stop) { | ||
return start * (1 - t) + stop * t; | ||
} | ||
|
||
export function ceil(n, base) { | ||
return base * Math.ceil(n / base); | ||
} | ||
|
||
export function floor(n, base) { | ||
return base * Math.floor(n / base); | ||
} | ||
|
||
export function round(n) { | ||
return Math.round(n * 1e12) / 1e12; | ||
} | ||
|
||
export function tickStep(min, max, count) { | ||
const e10 = Math.sqrt(50); | ||
const e5 = Math.sqrt(10); | ||
const e2 = Math.sqrt(2); | ||
const step0 = Math.abs(max - min) / Math.max(0, count); | ||
let step1 = 10 ** Math.floor(Math.log(step0) / Math.LN10); | ||
const error = step0 / step1; | ||
if (error >= e10) step1 *= 10; | ||
else if (error >= e5) step1 *= 5; | ||
else if (error >= e2) step1 *= 2; | ||
return step1; | ||
} | ||
|
||
export function nice(domain, interval) { | ||
const [min, max] = domain; | ||
return [interval.floor(min), interval.ceil(max)]; | ||
} | ||
|
||
export function ticks(min, max, count) { | ||
const step = tickStep(min, max, count); | ||
const start = Math.ceil(min / step); | ||
const stop = Math.floor(max / step); | ||
const n = Math.ceil(stop - start + 1); | ||
const values = new Array(n); | ||
for (let i = 0; i < n; i += 1) { | ||
values[i] = round((start + i) * step); | ||
} | ||
return values; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
export function createOrdinal({ domain, range }) { | ||
return (x) => { | ||
const index = domain.findIndex((d) => equal(d, x)); | ||
return range[index % range.length]; | ||
}; | ||
} | ||
|
||
export function equal(a, b) { | ||
return JSON.stringify(a) === JSON.stringify(b); | ||
} |