@visactor/vscale
Version:
Scales for visual encoding, used in VGrammar, VTable
184 lines (179 loc) • 10.8 kB
JavaScript
import { toNumber, isGreater, isLess, isNumber, isValid, isNil, clamp } from "@visactor/vutils";
import { OrdinalScale, implicit } from "./ordinal-scale";
import { bandSpace, calculateBandwidthFromWholeRangeSize, calculateWholeRangeFromRangeFactor, scaleWholeRangeSize } from "./utils/utils";
import { ScaleEnum } from "./type";
import { stepTicks, ticks } from "./utils/tick-sample-int";
export class BandScale extends OrdinalScale {
constructor(slience) {
super(), this.type = ScaleEnum.Band, this._range = [ 0, 1 ], this._step = void 0,
this._bandwidth = void 0, this._isFixed = !1, this._round = !1, this._paddingInner = 0,
this._paddingOuter = 0, this._align = .5, this._unknown = void 0, delete this.unknown,
this.rescale(slience);
}
rescale(slience, changeProperty) {
if (slience) return this;
this._wholeRange = null;
const wholeRange = this._calculateWholeRange(this._range, changeProperty), n = super.domain().length, reverse = wholeRange[1] < wholeRange[0];
let start = wholeRange[Number(reverse) - 0];
const stop = wholeRange[1 - Number(reverse)], space = bandSpace(n, this._paddingInner, this._paddingOuter);
return this._step = (stop - start) / Math.max(1, space || 1), this._round && (this._step = Math.floor(this._step)),
start += (stop - start - this._step * (n - this._paddingInner)) * this._align, this.isBandwidthFixed() || (this._bandwidth = this._step * (1 - this._paddingInner)),
this._round && (start = Math.round(start), this.isBandwidthFixed() || (this._bandwidth = Math.round(this._bandwidth))),
this._bandRangeState = {
reverse: reverse,
start: reverse ? clamp(start + this._step * (n - 1), wholeRange[1], wholeRange[0]) : clamp(start, wholeRange[0], wholeRange[1]),
min: reverse ? wholeRange[1] : wholeRange[0],
max: stop,
count: n
}, this.generateFishEyeTransform(), this;
}
scale(d) {
if (!this._bandRangeState) return;
const key = `${d}`, special = this._getSpecifiedValue(key);
if (void 0 !== special) return special;
let i = this._index.get(key);
if (!i) {
if (this._unknown !== implicit) return this._unknown;
i = this._domain.push(d), this._index.set(key, i);
}
const {count: count, start: start, reverse: reverse, min: min, max: max} = this._bandRangeState, output = start + (reverse ? -1 : 1) * ((i - 1) % count) * this._step;
return clamp(this._fishEyeTransform ? this._fishEyeTransform(output) : output, min, max);
}
_calculateWholeRange(range, changeProperty) {
if (this._wholeRange) return this._wholeRange;
if ((this._minBandwidth || this._maxBandwidth) && !this._isBandwidthFixedByUser()) {
let wholeSize;
if (isValid(this._rangeFactorStart) && isValid(this._rangeFactorEnd) && 2 === range.length) {
const wholeRange = calculateWholeRangeFromRangeFactor(range, [ this._rangeFactorStart, this._rangeFactorEnd ]);
wholeSize = Math.abs(wholeRange[1] - wholeRange[0]);
} else wholeSize = Math.abs(range[1] - range[0]);
const autoBandwidth = calculateBandwidthFromWholeRangeSize(super.domain().length, wholeSize, this._paddingInner, this._paddingOuter, this._round);
autoBandwidth < this._minBandwidth ? (this._bandwidth = this._minBandwidth, this._isFixed = !0) : autoBandwidth > this._maxBandwidth ? (this._bandwidth = this._maxBandwidth,
this._isFixed = !0) : (this._bandwidth = autoBandwidth, this._isFixed = !1);
}
if (this.isBandwidthFixed()) {
const wholeLength = scaleWholeRangeSize(super.domain().length, this._bandwidth, this._paddingInner, this._paddingOuter) * Math.sign(range[1] - range[0]), rangeFactorSize = Math.min((range[1] - range[0]) / wholeLength, 1);
if (isValid(this._rangeFactorStart) && isValid(this._rangeFactorEnd)) {
const canAlignStart = this._rangeFactorStart + rangeFactorSize <= 1, canAlignEnd = this._rangeFactorEnd - rangeFactorSize >= 0;
if ("rangeFactorStart" === changeProperty && canAlignStart ? this._rangeFactorEnd = this._rangeFactorStart + rangeFactorSize : "rangeFactorEnd" === changeProperty && canAlignEnd ? this._rangeFactorStart = this._rangeFactorEnd - rangeFactorSize : range[0] <= range[1] ? canAlignStart ? this._rangeFactorEnd = this._rangeFactorStart + rangeFactorSize : canAlignEnd ? this._rangeFactorStart = this._rangeFactorEnd - rangeFactorSize : (this._rangeFactorStart = 0,
this._rangeFactorEnd = rangeFactorSize) : canAlignEnd ? this._rangeFactorStart = this._rangeFactorEnd - rangeFactorSize : canAlignStart ? this._rangeFactorEnd = this._rangeFactorStart + rangeFactorSize : (this._rangeFactorStart = 1 - rangeFactorSize,
this._rangeFactorEnd = 1), wholeLength > 0) {
const r0 = range[0] - wholeLength * this._rangeFactorStart, r1 = r0 + wholeLength;
this._wholeRange = [ r0, r1 ];
} else {
const r1 = range[1] + wholeLength * (1 - this._rangeFactorEnd), r0 = r1 - wholeLength;
this._wholeRange = [ r0, r1 ];
}
} else this._rangeFactorStart = 0, this._rangeFactorEnd = rangeFactorSize, this._wholeRange = [ range[0], range[0] + wholeLength ];
return this._wholeRange;
}
return super._calculateWholeRange(range);
}
calculateWholeRangeSize() {
const wholeRange = this._calculateWholeRange(this._range);
return Math.abs(wholeRange[1] - wholeRange[0]);
}
calculateVisibleDomain(range) {
const domain = this._domain;
if (isValid(this._rangeFactorStart) && isValid(this._rangeFactorEnd) && domain.length) {
const d0 = this._getInvertIndex(range[0]), d1 = this._getInvertIndex(range[1]);
return domain.slice(Math.min(d0, d1), Math.max(d0, d1) + 1);
}
return domain;
}
domain(_, slience) {
return _ ? (super.domain(_), this.rescale(slience)) : super.domain();
}
range(_, slience) {
return _ ? (this._range = [ toNumber(_[0]), toNumber(_[1]) ], this.rescale(slience)) : this._range;
}
rangeRound(_, slience) {
return this._range = [ toNumber(_[0]), toNumber(_[1]) ], this._round = !0, this.rescale(slience);
}
ticks(count = 10) {
const d = this.calculateVisibleDomain(this._range);
if (-1 === count) return d;
return ticks(0, d.length - 1, count, !1).map((i => d[i]));
}
tickData(count = 10) {
return this.ticks(count).map(((tick, index) => ({
index: index,
tick: tick,
value: (this.scale(tick) - this._range[0] + this._bandwidth / 2) / (this._range[1] - this._range[0])
})));
}
forceTicks(count = 10) {
const d = this.calculateVisibleDomain(this._range);
return ticks(0, d.length - 1, count, !0).filter((i => i < d.length)).map((i => d[i]));
}
stepTicks(step) {
const d = this.calculateVisibleDomain(this._range);
return stepTicks(0, d.length - 1, step).map((i => d[i]));
}
_getInvertIndex(d) {
let i = 0;
const halfStep = this.step() / 2, halfBandwidth = this.bandwidth() / 2, len = this._domain.length, range = this.range(), reverse = range[0] > range[range.length - 1];
for (i = 0; i < len; i++) {
const r = this.scale(this._domain[i]) + halfBandwidth;
if (0 === i && (!reverse && !isGreater(d, r + halfStep) || reverse && !isLess(d, r - halfStep))) break;
if (i === len - 1) break;
if (!isLess(d, r - halfStep) && !isGreater(d, r + halfStep)) break;
}
return i >= 0 && i <= len - 1 ? i : len - 1;
}
invert(d) {
return this._domain[this._getInvertIndex(d)];
}
padding(p, slience) {
return void 0 !== p ? (this._paddingOuter = Math.max(0, Math.min(Array.isArray(p) ? Math.min.apply(null, p) : p)),
this._paddingInner = this._paddingOuter, this.rescale(slience)) : this._paddingInner;
}
paddingInner(_, slience) {
return void 0 !== _ ? (this._paddingInner = Math.max(0, Math.min(1, _)), this.rescale(slience)) : this._paddingInner;
}
paddingOuter(_, slience) {
return void 0 !== _ ? (this._paddingOuter = Math.max(0, Math.min(1, _)), this.rescale(slience)) : this._paddingOuter;
}
step() {
return this._step;
}
round(_, slience) {
return void 0 !== _ ? (this._round = _, this.rescale(slience)) : this._round;
}
align(_, slience) {
return void 0 !== _ ? (this._align = Math.max(0, Math.min(1, _)), this.rescale(slience)) : this._align;
}
rangeFactor(_, slience) {
return _ ? (super.rangeFactor(_), this.rescale(slience)) : super.rangeFactor();
}
rangeFactorStart(_, slience) {
return isNil(_) ? super.rangeFactorStart() : (super.rangeFactorStart(_), this.rescale(slience, "rangeFactorStart"));
}
rangeFactorEnd(_, slience) {
return isNil(_) ? super.rangeFactorEnd() : (super.rangeFactorEnd(_), this.rescale(slience, "rangeFactorEnd"));
}
bandwidth(_, slience) {
return _ ? ("auto" === _ ? (this._bandwidth = void 0, this._isFixed = !1) : (this._bandwidth = _,
this._isFixed = !0), this._userBandwidth = _, this.rescale(slience)) : this._bandwidth;
}
maxBandwidth(_, slience) {
return _ ? (this._maxBandwidth = "auto" === _ ? void 0 : _, this.rescale(slience)) : this._maxBandwidth;
}
minBandwidth(_, slience) {
return _ ? (this._minBandwidth = "auto" === _ ? void 0 : _, this.rescale(slience)) : this._minBandwidth;
}
fishEye(options, slience, clear) {
return options || clear ? (this._fishEyeOptions = options, this._fishEyeTransform = null,
this.rescale(slience)) : this._fishEyeOptions;
}
isBandwidthFixed() {
return this._isFixed && !!this._bandwidth;
}
_isBandwidthFixedByUser() {
return this._isFixed && this._userBandwidth && isNumber(this._userBandwidth);
}
clone() {
var _a, _b, _c;
return new BandScale(!0).domain(this._domain, !0).range(this._range, !0).round(this._round, !0).paddingInner(this._paddingInner, !0).paddingOuter(this._paddingOuter, !0).align(this._align, !0).bandwidth(null !== (_a = this._userBandwidth) && void 0 !== _a ? _a : "auto", !0).maxBandwidth(null !== (_b = this._maxBandwidth) && void 0 !== _b ? _b : "auto", !0).minBandwidth(null !== (_c = this._maxBandwidth) && void 0 !== _c ? _c : "auto");
}
}