Skip to content

Commit

Permalink
Merge pull request #76 from VisActor/feat/symLog-ticks
Browse files Browse the repository at this point in the history
Feat/sym log ticks
  • Loading branch information
xile611 authored Aug 24, 2023
2 parents 776adbb + 25cbde4 commit 4a57218
Show file tree
Hide file tree
Showing 11 changed files with 439 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@visactor/vdataset",
"comment": "feat(symLog): ticks caculation. feat visactor/vchart#508",
"type": "patch"
}
],
"packageName": "@visactor/vdataset"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@visactor/vscale",
"comment": "feat(symLog): ticks caculation. feat visactor/vchart#508",
"type": "patch"
}
],
"packageName": "@visactor/vscale"
}
5 changes: 4 additions & 1 deletion packages/vdataset/src/data-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@ export class DataView {
del: any;
};

constructor(public dataSet: DataSet, public options?: IDataViewOptions) {
constructor(
public dataSet: DataSet,
public options?: IDataViewOptions
) {
let name;
if (options?.name) {
name = options.name;
Expand Down
8 changes: 4 additions & 4 deletions packages/vscale/__tests__/log-nice-option.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ test('log.nice() width option forceMin', function () {
scale.nice(10, { forceMin: 0.5 });

expect(scale.domain()).toEqual([0.5, 10]);
expect(scale.ticks(5)).toEqual([0.5, 0.6, 0.7, 0.8, 0.9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
expect(scale.ticks(5)).toEqual([1, 2, 3, 4, 6, 10]);
expect(scale.domain()).toEqual([0.5, 10]);
expect(scale.scale(0.1)).toBeUndefined();
expect(scale.scale(-10)).toBeUndefined();
Expand All @@ -28,7 +28,7 @@ test('log.nice() width option forceMin', function () {
scale.nice(10, { forceMin: 2 });

expect(scale.domain()).toEqual([2, 10]);
expect(scale.ticks(5)).toEqual([2, 3, 4, 5, 6, 7, 8, 9, 10]);
expect(scale.ticks(5)).toEqual([2, 3, 4, 5, 6, 8, 10]);
expect(scale.domain()).toEqual([2, 10]);
expect(scale.scale(-20)).toBeUndefined();
expect(scale.scale(-10)).toBeUndefined();
Expand All @@ -42,7 +42,7 @@ test('log.nice() width option forceMax', function () {
scale.nice(10, { forceMax: 700 });

expect(scale.domain()).toEqual([100, 700]);
expect(scale.ticks(5)).toEqual([100, 200, 300, 400, 500, 600, 700]);
expect(scale.ticks(5)).toEqual([100, 158, 251, 398, 631, 700]);
expect(scale.domain()).toEqual([100, 700]);
expect(scale.scale(10)).toBeCloseTo(-1.1832946624549383);
expect(scale.scale(200)).toBeCloseTo(0.3562071871080222);
Expand All @@ -51,7 +51,7 @@ test('log.nice() width option forceMax', function () {
scale.nice(10, { forceMax: 1123 });

expect(scale.domain()).toEqual([100, 1123]);
expect(scale.ticks(5)).toEqual([100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]);
expect(scale.ticks(5)).toEqual([100, 158, 251, 398, 631, 1000, 1123]);
expect(scale.domain()).toEqual([100, 1123]);
expect(scale.scale(10)).toBeCloseTo(-0.952036626790323);
expect(scale.scale(200)).toBeCloseTo(0.28659158163464227);
Expand Down
89 changes: 39 additions & 50 deletions packages/vscale/__tests__/log.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ test('LogScale().domain(…) coerces values to numbers', () => {

it('log.domain(…) can take negative values', () => {
const x = new LogScale().domain([-100, -1]);
expect(x.ticks()).toEqual([-100, -90, -80, -70, -60, -50, -40, -30, -20, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1]);
expect(x.ticks()).toEqual([-100, -90, -55, -33, -20, -12, -7, -4, -3, -2, -1]);
expect(x.scale(-50)).toBeCloseTo(0.150515, 5);
});

Expand All @@ -48,10 +48,10 @@ it('log.domain(…) preserves specified domain exactly, with no floating point e
});

it('log.ticks(…) returns exact ticks, with no floating point error', () => {
expect(new LogScale().domain([0.15, 0.68]).ticks()).toEqual([0.2, 0.3, 0.4, 0.5, 0.6]);
expect(new LogScale().domain([0.68, 0.15]).ticks()).toEqual([0.6, 0.5, 0.4, 0.3, 0.2]);
expect(new LogScale().domain([-0.15, -0.68]).ticks()).toEqual([-0.2, -0.3, -0.4, -0.5, -0.6]);
expect(new LogScale().domain([-0.68, -0.15]).ticks()).toEqual([-0.6, -0.5, -0.4, -0.3, -0.2]);
expect(new LogScale().domain([0.15, 0.68]).ticks()).toEqual([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]);
expect(new LogScale().domain([0.68, 0.15]).ticks()).toEqual([0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1]);
expect(new LogScale().domain([-0.15, -0.68]).ticks()).toEqual([-0.1, -0.2, -0.3, -0.4, -0.5, -0.7]);
expect(new LogScale().domain([-0.68, -0.15]).ticks()).toEqual([-0.7, -0.5, -0.4, -0.3, -0.2, -0.1]);
});

it('log.range(…) does not coerce values to numbers', () => {
Expand Down Expand Up @@ -133,7 +133,7 @@ it('log.invert(y) coerces y to number', () => {
it('log.base(b) sets the log base, changing the ticks', () => {
const x = new LogScale().domain([1, 32]);

expect(x.base(2).ticks().map(x.tickFormat())).toEqual([1, 2, 4, 8, 16, 32]);
expect(x.base(2).ticks().map(x.tickFormat())).toEqual([1, 2, 3, 4, 6, 8, 11, 16, 23, 32]);
});

it('log.nice() nices the domain, extending it to powers of ten', () => {
Expand Down Expand Up @@ -239,22 +239,16 @@ it('log.clone() isolates changes to clamping', () => {

it('log.ticks() generates the expected power-of-ten for ascending ticks', () => {
const s = new LogScale();
expect(s.domain([1e-1, 1e1]).ticks().map(round)).toEqual([
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
]);
expect(s.domain([1e-1, 1]).ticks().map(round)).toEqual([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]);
expect(s.domain([-1, -1e-1]).ticks().map(round)).toEqual([-1, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1]);
expect(s.domain([1e-1, 1e1]).ticks().map(round)).toEqual([0, 1, 2, 3, 4, 6, 10]);
expect(s.domain([1e-1, 1]).ticks().map(round)).toEqual([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.8, 1]);
expect(s.domain([-1, -1e-1]).ticks().map(round)).toEqual([-1, -0.8, -0.7, -0.5, -0.4, -0.3, -0.2, -0.1]);
});

it('log.ticks() generates the expected power-of-ten ticks for descending domains', () => {
const s = new LogScale();
expect(s.domain([-1e-1, -1e1]).ticks().map(round)).toEqual(
[-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1].reverse()
);
expect(s.domain([-1e-1, -1]).ticks().map(round)).toEqual(
[-1, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1].reverse()
);
expect(s.domain([1, 1e-1]).ticks().map(round)).toEqual([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1].reverse());
expect(s.domain([-1e-1, -1e1]).ticks().map(round)).toEqual([-10, -7, -4, -3, -2, -1, -0].reverse());
expect(s.domain([-1e-1, -1]).ticks().map(round)).toEqual([-1, -0.8, -0.7, -0.5, -0.4, -0.3, -0.2, -0.1].reverse());
expect(s.domain([1, 1e-1]).ticks().map(round)).toEqual([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.8, 1].reverse());
});

it('log.ticks() generates the expected power-of-ten ticks for small domains', () => {
Expand All @@ -263,25 +257,27 @@ it('log.ticks() generates the expected power-of-ten ticks for small domains', ()
expect(s.domain([5, 1]).ticks()).toEqual([5, 4, 3, 2, 1]);
expect(s.domain([-1, -5]).ticks()).toEqual([-1, -2, -3, -4, -5]);
expect(s.domain([-5, -1]).ticks()).toEqual([-5, -4, -3, -2, -1]);
expect(s.domain([286.9252014, 329.4978332]).ticks(1)).toEqual([300]);
expect(s.domain([286.9252014, 329.4978332]).ticks(2)).toEqual([300]);
expect(s.domain([286.9252014, 329.4978332]).ticks(3)).toEqual([280, 300, 320, 340]);
expect(s.domain([286.9252014, 329.4978332]).ticks(4)).toEqual([280, 300, 320, 340]);
expect(s.domain([286.9252014, 329.4978332]).ticks()).toEqual([285, 290, 295, 300, 305, 310, 315, 320, 325, 330]);
expect(s.domain([286.9252014, 329.4978332]).ticks(1)).toEqual([287, 316, 329]);
expect(s.domain([286.9252014, 329.4978332]).ticks(2)).toEqual([287, 288, 302, 316, 329]);
expect(s.domain([286.9252014, 329.4978332]).ticks(3)).toEqual([287, 288, 302, 316, 329]);
expect(s.domain([286.9252014, 329.4978332]).ticks(4)).toEqual([287, 288, 302, 316, 329]);
expect(s.domain([286.9252014, 329.4978332]).ticks()).toEqual([
287, 288, 292, 295, 299, 302, 305, 309, 313, 316, 320, 324, 327, 329
]);
});

it('log.ticks() generates linear ticks when the domain extent is small', () => {
const s = new LogScale();
expect(s.domain([41, 42]).ticks()).toEqual([41, 41.1, 41.2, 41.3, 41.4, 41.5, 41.6, 41.7, 41.8, 41.9, 42]);
expect(s.domain([42, 41]).ticks()).toEqual([42, 41.9, 41.8, 41.7, 41.6, 41.5, 41.4, 41.3, 41.2, 41.1, 41]);
expect(s.domain([1600, 1400]).ticks()).toEqual([1600, 1580, 1560, 1540, 1520, 1500, 1480, 1460, 1440, 1420, 1400]);
expect(s.domain([41, 42]).ticks()).toEqual([41, 42]);
expect(s.domain([42, 41]).ticks()).toEqual([42, 41]);
expect(s.domain([1600, 1400]).ticks()).toEqual([
1600, 1585, 1567, 1549, 1531, 1514, 1496, 1479, 1462, 1445, 1429, 1413, 1400
]);
});

it('log.base(base).ticks() generates the expected power-of-base ticks', () => {
const s = new LogScale().base(Math.E);
expect(s.domain([0.1, 100]).ticks().map(round)).toEqual([
0.018315638889, 0.135335283237, 1, 7.389056098931, 54.598150033144, 403.428793492735
]);
expect(s.domain([0.1, 100]).ticks().map(round)).toEqual([0, 1, 5, 10, 50, 100]);
});

it('log.ticks() returns the empty array when the domain is degenerate', () => {
Expand All @@ -291,7 +287,7 @@ it('log.ticks() returns the empty array when the domain is degenerate', () => {
expect(x.domain([0, -1]).ticks()).toEqual([]);
expect(x.domain([-1, 0]).ticks()).toEqual([]);
expect(x.domain([-1, 1]).ticks()).toEqual([]);
expect(x.domain([0, 0]).ticks()).toEqual([]);
expect(x.domain([0, 0]).ticks()).toEqual([0]);
});

it('log.forceTicks() generates the expected power-of-ten for ascending ticks', () => {
Expand Down Expand Up @@ -325,30 +321,23 @@ it('log.forceTicks() return right tick count when the domain extent is small', (

it('log.forceTicks() return right tick count when the domain is degenerate', () => {
const x = new LogScale();
expect(x.domain([0, 1]).forceTicks()).toHaveLength(10);
expect(x.domain([1, 0]).forceTicks()).toHaveLength(10);
expect(x.domain([0, -1]).forceTicks()).toHaveLength(10);
expect(x.domain([-1, 0]).forceTicks()).toHaveLength(10);
expect(x.domain([-1, 1]).forceTicks()).toHaveLength(10);
expect(x.domain([0, 1]).forceTicks()).toHaveLength(0);
expect(x.domain([1, 0]).forceTicks()).toHaveLength(0);
expect(x.domain([0, -1]).forceTicks()).toHaveLength(0);
expect(x.domain([-1, 0]).forceTicks()).toHaveLength(0);
expect(x.domain([-1, 1]).forceTicks()).toHaveLength(0);
expect(x.domain([0, 0]).forceTicks()).toHaveLength(1);
});

it('log.forceTicks() return right tick count as input params', () => {
const s = new LogScale();
expect(s.domain([286.9252014, 329.4978332]).forceTicks(1)).toHaveLength(1);
expect(s.domain([286.9252014, 329.4978332]).forceTicks(2)).toHaveLength(2);
expect(s.domain([286.9252014, 329.4978332]).forceTicks(3)).toHaveLength(3);
expect(s.domain([286.9252014, 329.4978332]).forceTicks(4)).toHaveLength(4);
expect(s.domain([286.9252014, 329.4978332]).forceTicks()).toHaveLength(10);
});
// function round(x: number) {
// return Math.round(x * 1e12) / 1e12;
// }

it('log.stepTicks() return right tick as input params', () => {
const s = new LogScale();
expect(s.domain([286.9252014, 287.4978332]).stepTicks(1)).toEqual([286.9252014]);
expect(s.domain([286.9252014, 288.4978332]).stepTicks(1)).toEqual([286.9252014, 287.9252014]);
expect(s.domain([286.9252014, 289.4978332]).stepTicks(2)).toEqual([286.9252014, 288.9252014]);
expect(s.domain([286.9252014, 290.4978332]).stepTicks(2)).toEqual([286.9252014, 288.9252014]);
expect(s.domain([286.9252014, 291.4978332]).stepTicks(2)).toEqual([286.9252014, 288.9252014, 290.9252014]);
it('log.ticks(…) ', () => {
// expect(new LogScale().domain([1, 1000]).ticks(4)).toEqual([1, 10, 100, 1000]);
expect(new LogScale().domain([1, 16000]).base(Math.E).ticks(6)).toEqual([1, 10, 50, 500, 5000, 16000]);
expect(new LogScale().domain([2, 2048]).base(2).ticks()).toEqual([2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048]);
expect(new LogScale().domain([0, 1]).ticks()).toEqual([]);
});

function round(x: number) {
Expand Down
120 changes: 118 additions & 2 deletions packages/vscale/__tests__/symlog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ it('symlog.clone() returns a copy with changes to the domain are isolated', () =
expect(y.domain()).toEqual([2, 3]);

const y2 = x.domain([1, 1.9]).clone();
x.nice(5);
expect(x.domain()).toEqual([1, 2]);
x.nice();
expect(x.domain()).toEqual([0, 6.38905609893065]);
expect(y2.domain()).toEqual([1, 1.9]);
});

Expand Down Expand Up @@ -167,3 +167,119 @@ it('symlog().clamp(true).invert(x) cannot return a value outside the domain', ()
expect(x.invert(0)).toBe(1);
expect(x.invert(1)).toBe(20);
});

it('symlog.ticks() with positive domain', () => {
const x = new SymlogScale().domain([10, 100]).constant(10);
expect(x.ticks(3)).toEqual([10, 17, 35, 64, 100]);
expect(x.ticks(4)).toEqual([10, 17, 35, 64, 100]);
expect(x.ticks(5)).toEqual([10, 17, 35, 64, 100]);
expect(x.ticks(6)).toEqual([10, 12, 17, 23, 31, 40, 50, 64, 80, 100]);
expect(x.ticks(7)).toEqual([10, 12, 17, 23, 31, 40, 50, 64, 80, 100]);
expect(x.ticks(8)).toEqual([10, 12, 17, 23, 31, 40, 50, 64, 80, 100]);
expect(x.ticks(9)).toEqual([10, 12, 17, 23, 31, 40, 50, 64, 80, 100]);
expect(x.ticks(10)).toEqual([10, 12, 17, 23, 31, 40, 50, 64, 80, 100]);
});

it('symlog.ticks() can take negative values', () => {
const x = new SymlogScale().domain([-100, -1]);
expect(x.ticks()).toEqual([-100, -89, -54, -32, -19, -11, -6, -3, -2, -1]);
expect(x.scale(-50)).toBeCloseTo(0.1742222155862007, 5);
});

it('log.ticks() generates the expected power-of-ten for ascending ticks', () => {
const s = new SymlogScale();
expect(s.domain([1e-1, 1e1]).ticks().map(round)).toEqual([0, 1, 2, 3, 4, 5, 6, 8, 10]);
expect(s.domain([1e-1, 1]).ticks().map(round)).toEqual([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]);
expect(s.domain([-1, -1e-1]).ticks().map(round)).toEqual([-1, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1]);
});

it('symLog.ticks() generates the expected power-of-ten ticks for descending domains', () => {
const s = new SymlogScale();
expect(s.domain([-1e-1, -1e1]).ticks().map(round)).toEqual([-10, -8, -6, -5, -4, -3, -2, -1, -0].reverse());
expect(s.domain([-1e-1, -1]).ticks().map(round)).toEqual(
[-1, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1].reverse()
);
expect(s.domain([1, 1e-1]).ticks().map(round)).toEqual([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1].reverse());
});

it('symLog.ticks() generates the expected power-of-ten ticks for small domains', () => {
const s = new SymlogScale();
expect(s.domain([1, 5]).ticks()).toEqual([1, 2, 3, 4, 5]);
expect(s.domain([5, 1]).ticks()).toEqual([5, 4, 3, 2, 1]);
expect(s.domain([-1, -5]).ticks()).toEqual([-1, -2, -3, -4, -5]);
expect(s.domain([-5, -1]).ticks()).toEqual([-5, -4, -3, -2, -1]);
expect(s.domain([286.9252014, 329.4978332]).ticks(1)).toEqual([287, 298, 329]);
expect(s.domain([286.9252014, 329.4978332]).ticks(2)).toEqual([287, 298, 313, 329]);
expect(s.domain([286.9252014, 329.4978332]).ticks(3)).toEqual([287, 298, 313, 329]);
expect(s.domain([286.9252014, 329.4978332]).ticks(4)).toEqual([287, 298, 313, 329]);
expect(s.domain([286.9252014, 329.4978332]).ticks()).toEqual([
287, 289, 292, 295, 298, 301, 304, 307, 310, 313, 316, 320, 323, 326, 329
]);
});

it('symLog.ticks() generates linear ticks when the domain extent is small', () => {
const s = new SymlogScale();
expect(s.domain([41, 42]).ticks()).toEqual([41, 42]);
expect(s.domain([42, 41]).ticks()).toEqual([42, 41]);
expect(s.domain([1600, 1400]).ticks()).toEqual([
1600, 1587, 1571, 1555, 1540, 1524, 1509, 1494, 1479, 1465, 1450, 1436, 1421, 1407, 1400
]);
});

it('symLog.base(base).ticks() generates the expected power-of-base ticks', () => {
const s = new SymlogScale().constant(Math.E);
expect(s.domain([0.1, 100]).ticks().map(round)).toEqual([0, 2, 5, 10, 20, 50, 100]);
});

it('symLog.ticks() returns the empty array when the domain is degenerate', () => {
const x = new SymlogScale();
expect(x.domain([0, 1]).ticks()).toEqual([0, 1]);
expect(x.domain([1, 0]).ticks()).toEqual([1, 0]);
expect(x.domain([0, -1]).ticks()).toEqual([0, -1]);
expect(x.domain([-1, 0]).ticks()).toEqual([-1, -0]);
expect(x.domain([-1, 1]).ticks()).toEqual([-1, -0, 1]);
expect(x.domain([0, 0]).ticks()).toEqual([0]);
});

it('symLog.forceTicks() generates the expected power-of-ten for ascending ticks', () => {
const s = new SymlogScale();
expect(s.domain([1e-1, 1e1]).forceTicks().map(round)).toHaveLength(10);
expect(s.domain([1e-1, 1]).forceTicks().map(round)).toHaveLength(10);
expect(s.domain([-1, -1e-1]).forceTicks().map(round)).toHaveLength(10);
});

it('symLog.forceTicks() return right tick count for descending domains', () => {
const s = new SymlogScale();
expect(s.domain([-1e-1, -1e1]).forceTicks().map(round)).toHaveLength(10);
expect(s.domain([-1e-1, -1]).forceTicks().map(round)).toHaveLength(10);
expect(s.domain([1, 1e-1]).forceTicks().map(round)).toHaveLength(10);
});

it('symLog.forceTicks() return right tick count for small domains', () => {
const s = new SymlogScale();
expect(s.domain([1, 5]).forceTicks()).toHaveLength(10);
expect(s.domain([5, 1]).forceTicks()).toHaveLength(10);
expect(s.domain([-1, -5]).forceTicks()).toHaveLength(10);
expect(s.domain([-5, -1]).forceTicks()).toHaveLength(10);
});

it('symLog.forceTicks() return right tick count when the domain extent is small', () => {
const s = new SymlogScale();
expect(s.domain([41, 42]).forceTicks()).toHaveLength(10);
expect(s.domain([42, 41]).forceTicks()).toHaveLength(10);
expect(s.domain([1600, 1400]).forceTicks()).toHaveLength(10);
});

it('symLog.forceTicks() return right tick count when the domain is degenerate', () => {
const x = new SymlogScale();
expect(x.domain([0, 1]).forceTicks()).toHaveLength(10);
expect(x.domain([1, 0]).forceTicks()).toHaveLength(10);
expect(x.domain([0, -1]).forceTicks()).toHaveLength(10);
expect(x.domain([-1, 0]).forceTicks()).toHaveLength(10);
expect(x.domain([-1, 1]).forceTicks()).toHaveLength(10);
expect(x.domain([0, 0]).forceTicks()).toHaveLength(1);
});

function round(x: number) {
return Math.round(x * 1e12) / 1e12;
}
Loading

0 comments on commit 4a57218

Please sign in to comment.