This repository has been archived by the owner on Dec 10, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 271
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add encoder * feat: add encoder * refactor: revamp encoding types and derivations * test: add unit tests * fix: unit tests * test: add unit tests * fix: remove unused code * fix: channeltype
- Loading branch information
Showing
14 changed files
with
389 additions
and
4 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { flatMap } from 'lodash'; | ||
import { ChannelDef, TypedFieldDef } from '../types/ChannelDef'; | ||
import { MayBeArray } from '../types/Base'; | ||
import { isFieldDef } from '../typeGuards/ChannelDef'; | ||
import { isNotArray } from '../typeGuards/Base'; | ||
import ChannelEncoder from './ChannelEncoder'; | ||
import { | ||
EncodingConfig, | ||
DeriveEncoding, | ||
DeriveChannelTypes, | ||
DeriveChannelEncoders, | ||
} from '../types/Encoding'; | ||
|
||
export default class Encoder<Config extends EncodingConfig> { | ||
readonly encoding: DeriveEncoding<Config>; | ||
readonly channelTypes: DeriveChannelTypes<Config>; | ||
readonly channels: DeriveChannelEncoders<Config>; | ||
|
||
readonly legends: { | ||
[key: string]: (keyof Config)[]; | ||
}; | ||
|
||
constructor({ | ||
channelTypes, | ||
encoding, | ||
}: { | ||
channelTypes: DeriveChannelTypes<Config>; | ||
encoding: DeriveEncoding<Config>; | ||
}) { | ||
this.channelTypes = channelTypes; | ||
this.encoding = encoding; | ||
const channelNames = this.getChannelNames(); | ||
|
||
// Create channel encoders | ||
const channels: { [k in keyof Config]?: MayBeArray<ChannelEncoder<ChannelDef>> } = {}; | ||
|
||
channelNames.forEach(name => { | ||
const channelEncoding = encoding[name] as MayBeArray<ChannelDef>; | ||
if (Array.isArray(channelEncoding)) { | ||
const definitions = channelEncoding; | ||
channels[name] = definitions.map( | ||
(definition, i) => | ||
new ChannelEncoder({ | ||
channelType: channelTypes[name], | ||
definition, | ||
name: `${name}[${i}]`, | ||
}), | ||
); | ||
} else { | ||
const definition = channelEncoding; | ||
channels[name] = new ChannelEncoder({ | ||
channelType: channelTypes[name], | ||
definition, | ||
name: name as string, | ||
}); | ||
} | ||
}); | ||
|
||
this.channels = channels as DeriveChannelEncoders<Config>; | ||
|
||
// Group the channels that use the same field together | ||
// so they can share the same legend. | ||
this.legends = {}; | ||
channelNames | ||
.map(name => this.channels[name]) | ||
.forEach(c => { | ||
if (isNotArray(c) && c.hasLegend() && isFieldDef(c.definition)) { | ||
const name = c.name as keyof Config; | ||
const { field } = c.definition; | ||
if (this.legends[field]) { | ||
this.legends[field].push(name); | ||
} else { | ||
this.legends[field] = [name]; | ||
} | ||
} | ||
}); | ||
} | ||
|
||
getChannelNames() { | ||
return Object.keys(this.channelTypes) as (keyof Config)[]; | ||
} | ||
|
||
getChannelEncoders() { | ||
return this.getChannelNames().map(name => this.channels[name]); | ||
} | ||
|
||
getGroupBys() { | ||
const fields = flatMap(this.getChannelEncoders()) | ||
.filter(c => c.isGroupBy()) | ||
.map(c => (c.definition as TypedFieldDef).field!); | ||
|
||
return Array.from(new Set(fields)); | ||
} | ||
|
||
hasLegend() { | ||
return Object.keys(this.legends).length > 0; | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
packages/superset-ui-encodable/src/encoders/createEncoderFactory.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,43 @@ | ||
import Encoder from './Encoder'; | ||
import { EncodingConfig, DeriveChannelTypes, DeriveEncoding } from '../types/Encoding'; | ||
import mergeEncoding from '../utils/mergeEncoding'; | ||
|
||
type CreateEncoderFactoryParams<Config extends EncodingConfig> = { | ||
channelTypes: DeriveChannelTypes<Config>; | ||
} & ( | ||
| { | ||
/** | ||
* use the default approach to merge default encoding with user-specified encoding | ||
* if there are missing fields | ||
*/ | ||
defaultEncoding: DeriveEncoding<Config>; | ||
} | ||
| { | ||
/** | ||
* custom way to complete the encoding | ||
* if there are missing fields | ||
*/ | ||
completeEncoding: (e: Partial<DeriveEncoding<Config>>) => DeriveEncoding<Config>; | ||
}); | ||
|
||
export default function createEncoderFactory<Config extends EncodingConfig>( | ||
params: CreateEncoderFactoryParams<Config>, | ||
) { | ||
const { channelTypes } = params; | ||
type PartialEncoding = Partial<DeriveEncoding<Config>>; | ||
|
||
const completeEncoding = | ||
'defaultEncoding' in params | ||
? (encoding: PartialEncoding) => mergeEncoding(params.defaultEncoding, encoding) | ||
: params.completeEncoding; | ||
|
||
return { | ||
channelTypes, | ||
create: (encoding: PartialEncoding) => | ||
new Encoder<Config>({ | ||
channelTypes, | ||
encoding: completeEncoding(encoding), | ||
}), | ||
DEFAULT_ENCODING: completeEncoding({}), | ||
}; | ||
} |
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
18 changes: 18 additions & 0 deletions
18
packages/superset-ui-encodable/src/fillers/completeLegendConfig.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,18 @@ | ||
import { Value } from '../types/VegaLite'; | ||
import { Legend } from '../types/Legend'; | ||
import { ChannelType } from '../types/Channel'; | ||
import { ChannelDef } from '../types/ChannelDef'; | ||
import { isXOrY } from '../typeGuards/Channel'; | ||
|
||
export type CompleteLegendConfig = false | Legend; | ||
|
||
export default function completeLegendConfig<Output extends Value = Value>( | ||
channelType: ChannelType, | ||
channelDef: ChannelDef<Output>, | ||
): CompleteLegendConfig { | ||
if ('legend' in channelDef && channelDef.legend !== undefined) { | ||
return channelDef.legend; | ||
} | ||
|
||
return isXOrY(channelType) ? false : {}; | ||
} |
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,27 @@ | ||
import { ChannelType, ChannelTypeToDefMap } from './Channel'; | ||
import { Value } from './VegaLite'; | ||
import ChannelEncoder from '../encoders/ChannelEncoder'; | ||
|
||
export type EncodingConfig = { | ||
[k in string]: [ChannelType, Value, 'multiple'?]; | ||
}; | ||
|
||
export type DeriveChannelTypes<Config extends EncodingConfig> = { | ||
readonly [k in keyof Config]: Config[k]['0']; | ||
}; | ||
|
||
export type DeriveChannelOutputs<Config extends EncodingConfig> = { | ||
readonly [k in keyof Config]: Config[k]['1']; | ||
}; | ||
|
||
export type DeriveEncoding<Config extends EncodingConfig> = { | ||
[k in keyof Config]: Config[k]['2'] extends 'multiple' | ||
? ChannelTypeToDefMap<Config[k]['1']>[Config[k]['0']][] | ||
: ChannelTypeToDefMap<Config[k]['1']>[Config[k]['0']]; | ||
}; | ||
|
||
export type DeriveChannelEncoders<Config extends EncodingConfig> = { | ||
readonly [k in keyof Config]: Config[k]['2'] extends 'multiple' | ||
? ChannelEncoder<ChannelTypeToDefMap<Config[k]['1']>[Config[k]['0']]>[] | ||
: ChannelEncoder<ChannelTypeToDefMap<Config[k]['1']>[Config[k]['0']]>; | ||
}; |
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,5 +1,5 @@ | ||
export type Legend = boolean | null; | ||
export type Legend = {}; | ||
|
||
export interface WithLegend { | ||
legend?: Legend; | ||
legend?: boolean | Legend; | ||
} |
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,11 @@ | ||
import { EncodingConfig, DeriveEncoding } from '../types/Encoding'; | ||
|
||
export default function mergeEncoding<Config extends EncodingConfig>( | ||
defaultEncoding: DeriveEncoding<Config>, | ||
encoding: Partial<DeriveEncoding<Config>>, | ||
): DeriveEncoding<Config> { | ||
return { | ||
...defaultEncoding, | ||
...encoding, | ||
}; | ||
} |
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
64 changes: 64 additions & 0 deletions
64
packages/superset-ui-encodable/test/encoders/Encoder.test.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,64 @@ | ||
import createEncoderFactory from '../../src/encoders/createEncoderFactory'; | ||
|
||
describe('Encoder', () => { | ||
const factory = createEncoderFactory<{ | ||
x: ['X', number]; | ||
y: ['Y', number]; | ||
color: ['Color', string]; | ||
shape: ['Category', string]; | ||
tooltip: ['Text', string, 'multiple']; | ||
}>({ | ||
channelTypes: { | ||
x: 'X', | ||
y: 'Y', | ||
color: 'Color', | ||
shape: 'Category', | ||
tooltip: 'Text', | ||
}, | ||
defaultEncoding: { | ||
x: { type: 'quantitative', field: 'speed' }, | ||
y: { type: 'quantitative', field: 'price' }, | ||
color: { type: 'nominal', field: 'brand' }, | ||
shape: { type: 'nominal', field: 'brand' }, | ||
tooltip: [{ field: 'make' }, { field: 'model' }], | ||
}, | ||
}); | ||
|
||
const encoder = factory.create(); | ||
|
||
describe('new Encoder()', () => { | ||
it('creates new encoder', () => { | ||
expect(encoder).toBeDefined(); | ||
}); | ||
}); | ||
describe('.getChannelNames()', () => { | ||
it('returns an array of channel names', () => { | ||
expect(encoder.getChannelNames()).toEqual(['x', 'y', 'color', 'shape', 'tooltip']); | ||
}); | ||
}); | ||
describe('.getChannelEncoders()', () => { | ||
it('returns an array of channel encoders', () => { | ||
expect(encoder.getChannelEncoders()).toHaveLength(5); | ||
}); | ||
}); | ||
describe('.getGroupBys()', () => { | ||
it('returns an array of groupby fields', () => { | ||
expect(encoder.getGroupBys()).toEqual(['brand', 'make', 'model']); | ||
}); | ||
}); | ||
describe('.hasLegend()', () => { | ||
it('returns true if has legend', () => { | ||
expect(encoder.hasLegend()).toBeTruthy(); | ||
}); | ||
it('returns false if does not have legend', () => { | ||
expect( | ||
factory | ||
.create({ | ||
color: { type: 'nominal', field: 'brand', legend: false }, | ||
shape: { value: 'diamond' }, | ||
}) | ||
.hasLegend(), | ||
).toBeFalsy(); | ||
}); | ||
}); | ||
}); |
38 changes: 38 additions & 0 deletions
38
packages/superset-ui-encodable/test/encoders/createEncoderFactory.test.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,38 @@ | ||
import createEncoderFactory from '../../src/encoders/createEncoderFactory'; | ||
|
||
describe('createEncoderFactory()', () => { | ||
it('supports defaultEncoding as fixed value', () => { | ||
const factory = createEncoderFactory<{ | ||
x: ['X', number]; | ||
}>({ | ||
channelTypes: { | ||
x: 'X', | ||
}, | ||
defaultEncoding: { | ||
x: { type: 'quantitative', field: 'speed' }, | ||
}, | ||
}); | ||
|
||
const encoder = factory.create(); | ||
expect(encoder.encoding).toEqual({ | ||
x: { type: 'quantitative', field: 'speed' }, | ||
}); | ||
}); | ||
it('supports completeEncoding for customization', () => { | ||
const factory = createEncoderFactory<{ | ||
color: ['Color', string]; | ||
}>({ | ||
channelTypes: { | ||
color: 'Color', | ||
}, | ||
completeEncoding: () => ({ | ||
color: { value: 'red' }, | ||
}), | ||
}); | ||
|
||
const encoder = factory.create(); | ||
expect(encoder.encoding).toEqual({ | ||
color: { value: 'red' }, | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.