Skip to content
This repository has been archived by the owner on Dec 10, 2021. It is now read-only.

feat(encodable): add function for setting domain #256

Merged
merged 5 commits into from
Nov 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions packages/superset-ui-encodable/src/encoders/ChannelEncoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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>;
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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));
Expand All @@ -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)));
}
Expand Down
2 changes: 2 additions & 0 deletions packages/superset-ui-encodable/src/types/Scale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Original file line number Diff line number Diff line change
@@ -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
>[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ describe('ChannelEncoder', () => {
});
});

describe('.getDomain()', () => {
describe('.getDomainFromDataset()', () => {
describe('for ValueDef', () => {
it('returns an array of fixed value', () => {
const encoder = new ChannelEncoder({
Expand Down Expand Up @@ -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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down