diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/src/encoders/ChannelEncoder.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/src/encoders/ChannelEncoder.ts index 567ad8f3b8..08f305fea6 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/src/encoders/ChannelEncoder.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/src/encoders/ChannelEncoder.ts @@ -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 = (value: ChannelInput | Output) => Output | null | undefined; @@ -23,7 +27,7 @@ export default class ChannelEncoder, Output exten readonly channelType: ChannelType; readonly originalDefinition: Def; readonly definition: CompleteChannelDef; - readonly scale?: ReturnType; + readonly scale?: AllScale; readonly axis?: ChannelEncoderAxis; private readonly getValue: Getter; @@ -48,15 +52,15 @@ export default class ChannelEncoder, 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).value : identity; - } else { - this.encodeValue = (value: ChannelInput) => scale(value); - this.scale = scale; } if (this.definition.axis) { @@ -112,6 +116,21 @@ export default class ChannelEncoder, 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; } diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/src/parsers/scale/applyDomain.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/src/parsers/scale/applyDomain.ts index c00aa67de9..2e982f013c 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/src/parsers/scale/applyDomain.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/src/parsers/scale/applyDomain.ts @@ -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 ? (array: T[]) => array.slice().reverse() : (array: T[]) => array; + return reverse ? (array: T[]) => array.concat().reverse() : (array: T[]) => array; } export default function applyDomain( config: ScaleConfig, scale: D3Scale, - domainFromDataset?: string[] | number[] | boolean[] | Date[], + domainFromDataset?: ChannelInput[], ) { const { domain, reverse, type } = config; @@ -32,7 +34,7 @@ export default function applyDomain( 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( } } 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))); } diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/src/types/Scale.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/src/types/Scale.ts index 0026272697..be3a884f0c 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/src/types/Scale.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/src/types/Scale.ts @@ -230,3 +230,5 @@ export type D3Scale = | ScaleOrdinal | ScalePoint | ScaleBand; + +export type AllScale = D3Scale | ((val?: any) => string); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/src/utils/removeUndefinedAndNull.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/src/utils/removeUndefinedAndNull.ts new file mode 100644 index 0000000000..751383d3ec --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/src/utils/removeUndefinedAndNull.ts @@ -0,0 +1,6 @@ +export default function removeUndefinedAndNull(array: T[]) { + return array.filter(x => typeof x !== 'undefined' && x !== null) as Exclude< + T, + undefined | null + >[]; +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/test/encoders/ChannelEncoder.test.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/test/encoders/ChannelEncoder.test.ts index 1abb83e943..ab84de2b6f 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/test/encoders/ChannelEncoder.test.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/test/encoders/ChannelEncoder.test.ts @@ -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({ diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/test/parsers/scale/applyDomain.test.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/test/parsers/scale/applyDomain.test.ts index af8c644a8b..2f3a6082e9 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/test/parsers/scale/applyDomain.test.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodable/test/parsers/scale/applyDomain.test.ts @@ -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(); + 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', () => {