forked from apache/superset
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add channel encoder (apache#224)
* feat: add channel encoder * fix: all errors * fix: test * feat: complete channel encoder implementation and unit tests * fix: lint * fix: address comments * fix: lint
- Loading branch information
1 parent
0bf551b
commit 0780042
Showing
16 changed files
with
2,493 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
129 changes: 129 additions & 0 deletions
129
...ary_superset_ui/superset-ui/packages/superset-ui-encodable/src/encoders/ChannelEncoder.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import { extent as d3Extent } from 'd3-array'; | ||
import { ChannelType, ChannelInput } from '../types/Channel'; | ||
import { PlainObject, Dataset } from '../types/Data'; | ||
import { ChannelDef } from '../types/ChannelDef'; | ||
import createGetterFromChannelDef, { Getter } from '../parsers/createGetterFromChannelDef'; | ||
import completeChannelDef, { CompleteChannelDef } from '../fillers/completeChannelDef'; | ||
import createFormatterFromChannelDef from '../parsers/format/createFormatterFromChannelDef'; | ||
import createScaleFromScaleConfig from '../parsers/scale/createScaleFromScaleConfig'; | ||
import identity from '../utils/identity'; | ||
import { HasToString, IdentityFunction } from '../types/Base'; | ||
import { isTypedFieldDef, isValueDef } from '../typeGuards/ChannelDef'; | ||
import { isX, isY, isXOrY } from '../typeGuards/Channel'; | ||
import { Value } from '../types/VegaLite'; | ||
|
||
type EncodeFunction<Output> = (value: ChannelInput | Output) => Output | null | undefined; | ||
|
||
export default class ChannelEncoder<Def extends ChannelDef<Output>, Output extends Value = Value> { | ||
readonly name: string | Symbol | number; | ||
readonly channelType: ChannelType; | ||
readonly originalDefinition: Def; | ||
readonly definition: CompleteChannelDef<Output>; | ||
readonly scale: false | ReturnType<typeof createScaleFromScaleConfig>; | ||
|
||
private readonly getValue: Getter<Output>; | ||
readonly encodeValue: IdentityFunction<ChannelInput | Output> | EncodeFunction<Output>; | ||
readonly formatValue: (value: ChannelInput | HasToString) => string; | ||
|
||
constructor({ | ||
name, | ||
channelType, | ||
definition: originalDefinition, | ||
}: { | ||
name: string; | ||
channelType: ChannelType; | ||
definition: Def; | ||
}) { | ||
this.name = name; | ||
this.channelType = channelType; | ||
|
||
this.originalDefinition = originalDefinition; | ||
this.definition = completeChannelDef(this.channelType, originalDefinition); | ||
|
||
this.getValue = createGetterFromChannelDef(this.definition); | ||
this.formatValue = createFormatterFromChannelDef(this.definition); | ||
|
||
const scale = this.definition.scale && createScaleFromScaleConfig(this.definition.scale); | ||
this.encodeValue = scale === false ? identity : (value: ChannelInput) => scale(value); | ||
this.scale = scale; | ||
} | ||
|
||
encodeDatum: { | ||
(datum: PlainObject): Output | null | undefined; | ||
(datum: PlainObject, otherwise: Output): Output; | ||
} = (datum: PlainObject, otherwise?: Output) => { | ||
const value = this.getValueFromDatum(datum); | ||
|
||
if (otherwise !== undefined && (value === null || value === undefined)) { | ||
return otherwise; | ||
} | ||
|
||
return this.encodeValue(value) as Output; | ||
}; | ||
|
||
formatDatum = (datum: PlainObject): string => this.formatValue(this.getValueFromDatum(datum)); | ||
|
||
getValueFromDatum = <T extends ChannelInput | Output>(datum: PlainObject, otherwise?: T) => { | ||
const value = this.getValue(datum); | ||
|
||
return otherwise !== undefined && (value === null || value === undefined) | ||
? otherwise | ||
: (value as T); | ||
}; | ||
|
||
getDomain = (data: Dataset) => { | ||
if (isValueDef(this.definition)) { | ||
const { value } = this.definition; | ||
|
||
return [value]; | ||
} | ||
|
||
const { type } = this.definition; | ||
if (type === 'nominal' || type === 'ordinal') { | ||
return Array.from(new Set(data.map(d => this.getValueFromDatum(d)))) as string[]; | ||
} else if (type === 'quantitative') { | ||
const extent = d3Extent(data, d => this.getValueFromDatum<number>(d)); | ||
|
||
return typeof extent[0] === 'undefined' ? [0, 1] : (extent as [number, number]); | ||
} else if (type === 'temporal') { | ||
const extent = d3Extent(data, d => this.getValueFromDatum<number | Date>(d)); | ||
|
||
return typeof extent[0] === 'undefined' | ||
? [0, 1] | ||
: (extent as [number, number] | [Date, Date]); | ||
} | ||
|
||
return []; | ||
}; | ||
|
||
getTitle() { | ||
return this.definition.title; | ||
} | ||
|
||
isGroupBy() { | ||
if (isTypedFieldDef(this.definition)) { | ||
const { type } = this.definition; | ||
|
||
return ( | ||
this.channelType === 'Category' || | ||
this.channelType === 'Text' || | ||
(this.channelType === 'Color' && (type === 'nominal' || type === 'ordinal')) || | ||
(isXOrY(this.channelType) && (type === 'nominal' || type === 'ordinal')) | ||
); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
isX() { | ||
return isX(this.channelType); | ||
} | ||
|
||
isXOrY() { | ||
return isXOrY(this.channelType); | ||
} | ||
|
||
isY() { | ||
return isY(this.channelType); | ||
} | ||
} |
36 changes: 30 additions & 6 deletions
36
..._superset_ui/superset-ui/packages/superset-ui-encodable/src/fillers/completeChannelDef.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
...rary_superset_ui/superset-ui/packages/superset-ui-encodable/src/fillers/inferFieldType.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { ChannelType } from '../types/Channel'; | ||
import { isXOrY } from '../typeGuards/Channel'; | ||
import { Type } from '../types/VegaLite'; | ||
|
||
const temporalFieldNames = new Set(['time', 'date', 'datetime', 'timestamp']); | ||
|
||
export default function inferFieldType(channelType: ChannelType, field: string = ''): Type { | ||
if (isXOrY(channelType) || channelType === 'Numeric') { | ||
return temporalFieldNames.has(field.toLowerCase()) ? 'temporal' : 'quantitative'; | ||
} | ||
|
||
return 'nominal'; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { default as ChannelEncoder } from './encoders/ChannelEncoder'; | ||
export { default as completeChannelDef } from './fillers/completeChannelDef'; | ||
export { default as createScaleFromScaleConfig } from './parsers/scale/createScaleFromScaleConfig'; |
13 changes: 8 additions & 5 deletions
13
...t_ui/superset-ui/packages/superset-ui-encodable/src/parsers/createGetterFromChannelDef.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,20 @@ | ||
import { get } from 'lodash/fp'; | ||
import identity from '../utils/identity'; | ||
import { ChannelDef } from '../types/ChannelDef'; | ||
import { isValueDef } from '../typeGuards/ChannelDef'; | ||
import { PlainObject } from '../types/Data'; | ||
import { Value } from '../types/VegaLite'; | ||
import { ChannelInput } from '../types/Channel'; | ||
|
||
export default function createGetterFromChannelDef( | ||
definition: ChannelDef, | ||
): (x?: PlainObject) => any { | ||
export type Getter<Output extends Value> = (x?: PlainObject) => ChannelInput | Output | undefined; | ||
|
||
export default function createGetterFromChannelDef<Output extends Value>( | ||
definition: ChannelDef<Output>, | ||
): Getter<Output> { | ||
if (isValueDef(definition)) { | ||
return () => definition.value; | ||
} else if (typeof definition.field !== 'undefined') { | ||
return get(definition.field); | ||
} | ||
|
||
return identity; | ||
return () => undefined; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.