mirror of
https://github.com/apache/superset.git
synced 2024-09-19 12:09:42 -04:00
feat(encodable): add function for setting domain (#256)
* feat: add scale helper * feat: add functions * fix: remove unused import * fix: add unit tests * fix: unit tes
This commit is contained in:
parent
e719c19365
commit
1772b671cf
@ -13,8 +13,12 @@ import completeChannelDef, {
|
||||
CompleteValueDef,
|
||||
} from '../fillers/completeChannelDef';
|
||||
import createFormatterFromChannelDef from '../parsers/format/createFormatterFromChannelDef';
|
||||
import createScaleFromScaleConfig from '../parsers/scale/createScaleFromScaleConfig';
|
||||
import identity from '../utils/identity';
|
||||
import applyDomain from '../parsers/scale/applyDomain';
|
||||
import applyZero from '../parsers/scale/applyZero';
|
||||
import applyNice from '../parsers/scale/applyNice';
|
||||
import { AllScale } from '../types/Scale';
|
||||
import { createScaleFromScaleConfig } from '..';
|
||||
|
||||
type EncodeFunction<Output> = (value: ChannelInput | Output) => Output | null | undefined;
|
||||
|
||||
@ -23,7 +27,7 @@ export default class ChannelEncoder<Def extends ChannelDef<Output>, Output exten
|
||||
readonly channelType: ChannelType;
|
||||
readonly originalDefinition: Def;
|
||||
readonly definition: CompleteChannelDef<Output>;
|
||||
readonly scale?: ReturnType<typeof createScaleFromScaleConfig>;
|
||||
readonly scale?: AllScale<Output>;
|
||||
readonly axis?: ChannelEncoderAxis<Def, Output>;
|
||||
|
||||
private readonly getValue: Getter<Output>;
|
||||
@ -48,15 +52,15 @@ export default class ChannelEncoder<Def extends ChannelDef<Output>, Output exten
|
||||
this.getValue = createGetterFromChannelDef(this.definition);
|
||||
this.formatValue = createFormatterFromChannelDef(this.definition);
|
||||
|
||||
const scale = this.definition.scale && createScaleFromScaleConfig(this.definition.scale);
|
||||
if (scale === false) {
|
||||
if (this.definition.scale) {
|
||||
const scale = createScaleFromScaleConfig(this.definition.scale);
|
||||
this.encodeValue = (value: ChannelInput) => scale(value);
|
||||
this.scale = scale;
|
||||
} else {
|
||||
this.encodeValue =
|
||||
'value' in this.definition
|
||||
? () => (this.definition as CompleteValueDef<Output>).value
|
||||
: identity;
|
||||
} else {
|
||||
this.encodeValue = (value: ChannelInput) => scale(value);
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
if (this.definition.axis) {
|
||||
@ -112,6 +116,21 @@ export default class ChannelEncoder<Def extends ChannelDef<Output>, Output exten
|
||||
return [];
|
||||
};
|
||||
|
||||
setDomain(domain: ChannelInput[]) {
|
||||
if (this.definition.scale !== false && this.scale && 'domain' in this.scale) {
|
||||
const config = this.definition.scale;
|
||||
applyDomain(config, this.scale, domain);
|
||||
applyZero(config, this.scale);
|
||||
applyNice(config, this.scale);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setDomainFromDataset(data: Dataset) {
|
||||
return this.scale ? this.setDomain(this.getDomainFromDataset(data)) : this;
|
||||
}
|
||||
|
||||
getTitle() {
|
||||
return this.definition.title;
|
||||
}
|
||||
|
@ -7,15 +7,17 @@ import parseDateTimeIfPossible from '../parseDateTimeIfPossible';
|
||||
import parseContinuousDomain from '../domain/parseContinuousDomain';
|
||||
import parseDiscreteDomain from '../domain/parseDiscreteDomain';
|
||||
import combineContinuousDomains from '../../utils/combineContinuousDomains';
|
||||
import { ChannelInput } from '../../types/Channel';
|
||||
import removeUndefinedAndNull from '../../utils/removeUndefinedAndNull';
|
||||
|
||||
function createOrderFunction(reverse: boolean | undefined) {
|
||||
return reverse ? <T>(array: T[]) => array.slice().reverse() : <T>(array: T[]) => array;
|
||||
return reverse ? <T>(array: T[]) => array.concat().reverse() : <T>(array: T[]) => array;
|
||||
}
|
||||
|
||||
export default function applyDomain<Output extends Value>(
|
||||
config: ScaleConfig<Output>,
|
||||
scale: D3Scale<Output>,
|
||||
domainFromDataset?: string[] | number[] | boolean[] | Date[],
|
||||
domainFromDataset?: ChannelInput[],
|
||||
) {
|
||||
const { domain, reverse, type } = config;
|
||||
|
||||
@ -32,7 +34,7 @@ export default function applyDomain<Output extends Value>(
|
||||
if (isContinuousScale(scale, type)) {
|
||||
const combined = combineContinuousDomains(
|
||||
parseContinuousDomain(fixedDomain, type),
|
||||
inputDomain && parseContinuousDomain(inputDomain, type),
|
||||
inputDomain && removeUndefinedAndNull(parseContinuousDomain(inputDomain, type)),
|
||||
);
|
||||
if (combined) {
|
||||
scale.domain(order(combined));
|
||||
@ -49,7 +51,7 @@ export default function applyDomain<Output extends Value>(
|
||||
}
|
||||
} else if (inputDomain) {
|
||||
if (isContinuousScale(scale, type)) {
|
||||
scale.domain(order(parseContinuousDomain(inputDomain, type)));
|
||||
scale.domain(order(removeUndefinedAndNull(parseContinuousDomain(inputDomain, type))));
|
||||
} else {
|
||||
scale.domain(order(parseDiscreteDomain(inputDomain)));
|
||||
}
|
||||
|
@ -230,3 +230,5 @@ export type D3Scale<Output extends Value = Value> =
|
||||
| ScaleOrdinal<CategoricalScaleInput, Output>
|
||||
| ScalePoint<CategoricalScaleInput>
|
||||
| ScaleBand<CategoricalScaleInput>;
|
||||
|
||||
export type AllScale<Output extends Value = Value> = D3Scale<Output> | ((val?: any) => string);
|
||||
|
@ -0,0 +1,6 @@
|
||||
export default function removeUndefinedAndNull<T>(array: T[]) {
|
||||
return array.filter(x => typeof x !== 'undefined' && x !== null) as Exclude<
|
||||
T,
|
||||
undefined | null
|
||||
>[];
|
||||
}
|
@ -117,7 +117,7 @@ describe('ChannelEncoder', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('.getDomain()', () => {
|
||||
describe('.getDomainFromDataset()', () => {
|
||||
describe('for ValueDef', () => {
|
||||
it('returns an array of fixed value', () => {
|
||||
const encoder = new ChannelEncoder({
|
||||
@ -232,6 +232,93 @@ describe('ChannelEncoder', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('.setDomain()', () => {
|
||||
it('sets the domain', () => {
|
||||
const encoder = new ChannelEncoder({
|
||||
name: 'x',
|
||||
channelType: 'X',
|
||||
definition: {
|
||||
type: 'quantitative',
|
||||
field: 'speed',
|
||||
title: 'Speed',
|
||||
scale: { zero: false },
|
||||
},
|
||||
});
|
||||
expect(encoder.setDomain([20, 30])).toEqual(encoder);
|
||||
expect('domain' in encoder.scale && encoder.scale!.domain()).toEqual([20, 30]);
|
||||
});
|
||||
it('sets the domain (with zero)', () => {
|
||||
const encoder = new ChannelEncoder({
|
||||
name: 'x',
|
||||
channelType: 'X',
|
||||
definition: {
|
||||
type: 'quantitative',
|
||||
field: 'speed',
|
||||
title: 'Speed',
|
||||
},
|
||||
});
|
||||
expect(encoder.setDomain([20, 30])).toEqual(encoder);
|
||||
expect('domain' in encoder.scale && encoder.scale!.domain()).toEqual([0, 30]);
|
||||
});
|
||||
it('sets the domain (with nice)', () => {
|
||||
const encoder = new ChannelEncoder({
|
||||
name: 'x',
|
||||
channelType: 'X',
|
||||
definition: {
|
||||
type: 'quantitative',
|
||||
field: 'speed',
|
||||
title: 'Speed',
|
||||
scale: { zero: false },
|
||||
},
|
||||
});
|
||||
expect(encoder.setDomain([21.5, 30])).toEqual(encoder);
|
||||
expect('domain' in encoder.scale && encoder.scale!.domain()).toEqual([21, 30]);
|
||||
});
|
||||
it('does nothing if does not have scale', () => {
|
||||
const encoder = new ChannelEncoder({
|
||||
name: 'x',
|
||||
channelType: 'X',
|
||||
definition: {
|
||||
type: 'quantitative',
|
||||
field: 'speed',
|
||||
title: 'Speed',
|
||||
scale: false,
|
||||
},
|
||||
});
|
||||
expect(encoder.setDomain([21.5, 30])).toEqual(encoder);
|
||||
expect(encoder.scale).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.setDomainFromDataset()', () => {
|
||||
it('sets the domain', () => {
|
||||
const encoder = new ChannelEncoder({
|
||||
name: 'x',
|
||||
channelType: 'X',
|
||||
definition: {
|
||||
type: 'quantitative',
|
||||
field: 'price',
|
||||
scale: { zero: false },
|
||||
},
|
||||
});
|
||||
expect(encoder.setDomainFromDataset([{ price: 1 }, { price: 5 }])).toEqual(encoder);
|
||||
expect('domain' in encoder.scale && encoder.scale!.domain()).toEqual([1, 5]);
|
||||
});
|
||||
it('does nothing if does not have scale', () => {
|
||||
const encoder = new ChannelEncoder({
|
||||
name: 'x',
|
||||
channelType: 'X',
|
||||
definition: {
|
||||
type: 'quantitative',
|
||||
field: 'price',
|
||||
scale: false,
|
||||
},
|
||||
});
|
||||
expect(encoder.setDomainFromDataset([{ price: 1 }, { price: 5 }])).toEqual(encoder);
|
||||
expect(encoder.scale).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.getTitle()', () => {
|
||||
it('returns title', () => {
|
||||
const encoder = new ChannelEncoder({
|
||||
|
@ -19,6 +19,20 @@ describe('applyDomain()', () => {
|
||||
);
|
||||
expect(scale.domain()).toEqual(['a', 'c', 'b']);
|
||||
});
|
||||
it('continuous domain (reverse)', () => {
|
||||
const scale = scaleLinear();
|
||||
applyDomain({ type: 'linear', domain: [null, 10], reverse: true }, scale, [1, 20]);
|
||||
expect(scale.domain()).toEqual([10, 1]);
|
||||
});
|
||||
it('discrete domain (reverse)', () => {
|
||||
const scale = scaleOrdinal<HasToString, string>();
|
||||
applyDomain(
|
||||
{ type: 'ordinal', domain: ['a', 'c'], range: ['red', 'green', 'blue'], reverse: true },
|
||||
scale,
|
||||
['a', 'b', 'c'],
|
||||
);
|
||||
expect(scale.domain()).toEqual(['b', 'c', 'a']);
|
||||
});
|
||||
});
|
||||
describe('without domainFromDataset', () => {
|
||||
it('continuous domain', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user