Skip to content

Commit

Permalink
feat(viz): redo log scale & ticks, restructure all files
Browse files Browse the repository at this point in the history
- add dataMinLog/dataMaxLog/dataBounds2Log()
  • Loading branch information
postspectacular committed Sep 12, 2020
1 parent b5a8f53 commit 2f51668
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 89 deletions.
1 change: 1 addition & 0 deletions packages/viz/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"doc": "node_modules/.bin/typedoc --mode modules --out doc src",
"pub": "yarn build:release && yarn publish --access public",
"tool:candles": "ts-node -P tools/tsconfig.json tools/candles.ts",
"tool:line": "ts-node -P tools/tsconfig.json tools/line.ts",
"tool:intervals": "ts-node -P tools/tsconfig.json tools/intervals.ts",
"tool:tags": "ts-node -P tools/tsconfig.json tools/tagcloud.ts"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/viz/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export interface AxisSpec {
minor: Partial<TickSpec>;
}

export type InitialAxisSpec = Partial<AxisSpec> &
Pick<AxisSpec, "domain" | "range" | "pos">;

export interface TickSpec {
ticks: Fn<Domain, Iterable<number>>;
size: number;
Expand Down
52 changes: 0 additions & 52 deletions packages/viz/src/axis.ts

This file was deleted.

17 changes: 17 additions & 0 deletions packages/viz/src/axis/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { float } from "@thi.ng/strings";
import type { AxisSpec } from "../api";

export const axisDefaults = (extra?: any): Partial<AxisSpec> => ({
attribs: { stroke: "#000" },
label: (pos, body) => ["text", {}, pos, body],
labelAttribs: {
fill: "#000",
stroke: "none",
},
labelOffset: [0, 0],
format: float(2),
visible: true,
major: { ticks: () => [], size: 10 },
minor: { ticks: () => [], size: 5 },
...extra,
});
35 changes: 35 additions & 0 deletions packages/viz/src/axis/lens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { mergeDeepObj } from "@thi.ng/associative";
import { lens, mix, safeDiv } from "@thi.ng/math";
import type { AxisSpec, Domain, InitialAxisSpec, Range, ScaleFn } from "../api";
import { axisDefaults } from "./common";

export const lensScale = (
[d1, d2]: Domain,
[r1, r2]: Range,
focus = (d1 + d2) / 2,
strength: number
): ScaleFn => {
const dr = d2 - d1;
const f = safeDiv(focus - d1, dr);
return (x) => mix(r1, r2, lens(f, strength, safeDiv(x - d1, dr)));
};

export const lensAxis = (
src: InitialAxisSpec & { focus?: number; strength?: number }
): AxisSpec => {
const spec = mergeDeepObj(
axisDefaults({
focus: (src.domain[0] + src.domain[1]) / 2,
strength: 1,
}),
src
);
!spec.scale &&
(spec.scale = lensScale(
spec.domain,
spec.range,
spec.focus,
spec.strength
));
return spec;
};
22 changes: 22 additions & 0 deletions packages/viz/src/axis/linear.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { mergeDeepObj } from "@thi.ng/associative";
import { fit, inRange, roundTo } from "@thi.ng/math";
import { filter, range } from "@thi.ng/transducers";
import type { AxisSpec, Domain, InitialAxisSpec, Range, ScaleFn } from "../api";
import { axisDefaults } from "./common";

export const linearScale = ([d1, d2]: Domain, [r1, r2]: Range): ScaleFn => (
x
) => fit(x, d1, d2, r1, r2);

export const linearAxis = (src: InitialAxisSpec) => {
const spec = <AxisSpec>mergeDeepObj(axisDefaults(), src);
!spec.scale && (spec.scale = linearScale(spec.domain, spec.range));
return spec;
};

export const linearTicks = (step: number) => ([d1, d2]: Domain) => [
...filter(
(x) => inRange(x, d1, d2),
range(roundTo(d1, step), d2 + step, step)
),
];
78 changes: 78 additions & 0 deletions packages/viz/src/axis/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { FnN, FnU3 } from "@thi.ng/api";
import { mergeDeepObj } from "@thi.ng/associative";
import { inRange, mix } from "@thi.ng/math";
import {
comp,
filter,
iterator,
map,
range,
range2d,
} from "@thi.ng/transducers";
import type { AxisSpec, Domain, InitialAxisSpec, Range, ScaleFn } from "../api";
import { axisDefaults } from "./common";

/** @internal */
export const log = (base: number): FnN => {
const lb = 1 / Math.log(base);
return (x) => (x > 0 ? Math.log(x) * lb : x < 0 ? -Math.log(-x) * lb : 0);
};

export const logScale__ = (
[d1, d2]: Domain,
[r1, r2]: Range,
base = 10
): ScaleFn => {
const $ = log(base);
const dr = 1 / $(d2 - d1 + 1);
return (x) => mix(r1, r2, $(x - d1 + 1) * dr);
};

export const logScale = (
[d1, d2]: Domain,
[r1, r2]: Range,
base = 10
): ScaleFn => {
const $ = log(base);
const d11 = $(d1);
const d3 = $(d2) - d11;
return (x) => mix(r1, r2, ($(x) - d11) / d3);
};

export const logAxis = (src: InitialAxisSpec & { base?: number }): AxisSpec => {
const spec = mergeDeepObj(axisDefaults({ base: 10 }), src);
!spec.scale && (spec.scale = logScale(spec.domain, spec.range, spec.base));
return spec;
};

/** @internal */
export const logDomain: FnU3<number, number[]> = (d1, d2, base) => {
const $ = log(base);
return [Math.floor($(d1)), Math.ceil($(d2))];
};

export const logTicksMajor = (base = 10) => ([d1, d2]: Domain) => {
const [d1l, d2l] = logDomain(d1, d2, base);
return [
...iterator(
comp(
map((x) => Math.pow(base, x)),
filter((x) => inRange(x, d1, d2))
),
range(d1l, d2l + 1)
),
];
};

export const logTicksMinor = (base = 10) => ([d1, d2]: Domain) => {
const [d1l, d2l] = logDomain(d1, d2, base);
return [
...iterator(
comp(
map(([m, n]) => (m * Math.pow(base, n)) / base),
filter((x) => inRange(x, d1, d2))
),
range2d(1, base, d1l, d2l + 1)
),
];
};
13 changes: 13 additions & 0 deletions packages/viz/src/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,16 @@ export const dataMin = <T>(fn: Fn<T, number>, src: T[], pad = 0) =>

export const dataMax = <T>(fn: Fn<T, number>, src: T[], pad = 0) =>
transduce(map(fn), max(), src) + pad;

export const dataMinLog = <T>(fn: Fn<T, number>, src: T[], base = 10) =>
Math.pow(base, Math.floor(Math.log(dataMin(fn, src)) / Math.log(base)));

export const dataMaxLog = <T>(fn: Fn<T, number>, src: T[], base = 10) =>
Math.pow(base, Math.ceil(Math.log(dataMax(fn, src)) / Math.log(base)));

export const dataBounds2Log = <T>(
min: Fn<T, number>,
max: Fn<T, number>,
src: T[],
base = 10
) => [dataMinLog(min, src, base), dataMaxLog(max, src, base)];
7 changes: 4 additions & 3 deletions packages/viz/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
export * from "./api";
export * from "./axis";
export * from "./date";
export * from "./domain";
export * from "./plot";
export * from "./scale";

export * from "./axis/lens";
export * from "./axis/linear";
export * from "./axis/log";

export * from "./plot/area";
export * from "./plot/binpack";
export * from "./plot/candle";
export * from "./plot/line";
export * from "./plot/scatter";
Expand Down
6 changes: 3 additions & 3 deletions packages/viz/src/plot/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import type { Fn } from "@thi.ng/api";
import { clamp, inRange } from "@thi.ng/math";
import type { AxisSpec, VizSpec } from "../api";

/** @internal */
export const valueMapper = (
{ scale: scaleX }: AxisSpec,
{ scale: scaleY, domain: [dmin, dmax] }: AxisSpec,
project: Fn<number[], number[]> = identity
project: Fn<number[], number[]> = (x) => x
) => ([x, y]: number[]) => project([scaleX(x), scaleY(clamp(y, dmin, dmax))]);

const identity = (x: any) => x;

/** @internal */
export function* processedPoints(
{ xaxis, yaxis, project }: VizSpec,
data: Iterable<number[]>
Expand Down
31 changes: 0 additions & 31 deletions packages/viz/src/scale.ts

This file was deleted.

0 comments on commit 2f51668

Please sign in to comment.