Skip to content

Commit

Permalink
feat: simplify Encoder creation
Browse files Browse the repository at this point in the history
  • Loading branch information
kristw authored and zhaoyongjie committed Nov 26, 2021
1 parent c469e58 commit 52d120a
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Value } from 'vega-lite/build/src/channeldef';
import AbstractEncoder from '../encodeable/AbstractEncoder';
import { PartialSpec } from '../encodeable/types/Specification';
import { ChannelTypeToDefMap } from '../encodeable/types/Channel';
import { ExtractChannelOutput } from '../encodeable/types/ChannelDef';
import createEncoderClass from '../encodeable/createEncoderClass';

/**
* Define channel types
Expand All @@ -16,6 +15,7 @@ const channelTypes = {
export type ChannelTypes = typeof channelTypes;

/**
* TEMPLATE:
* Helper for defining encoding
*/
type CreateChannelDef<
Expand All @@ -33,23 +33,19 @@ export type Encoding = {
};

/**
* TEMPLATE:
* Can use this to get returned type of a Channel
* example usage: ChannelOutput<'x'>
*/
export type ChannelOutput<ChannelName extends keyof Encoding> = ExtractChannelOutput<
Encoding[ChannelName]
>;

export default class Encoder extends AbstractEncoder<ChannelTypes, Encoding> {
static readonly DEFAULT_ENCODINGS: Encoding = {
export default class Encoder extends createEncoderClass<ChannelTypes, Encoding>({
channelTypes,
defaultEncoding: {
color: { value: '#222' },
x: { field: 'x', type: 'nominal' },
y: { field: 'y', type: 'quantitative' },
};

static readonly CHANNEL_OPTIONS = {};

constructor(spec: PartialSpec<Encoding>) {
super(channelTypes, spec, Encoder.DEFAULT_ENCODINGS, Encoder.CHANNEL_OPTIONS);
}
}
},
}) {}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Value } from 'vega-lite/build/src/channeldef';
import AbstractEncoder from '../encodeable/AbstractEncoder';
import { PartialSpec } from '../encodeable/types/Specification';
import { ChannelTypeToDefMap } from '../encodeable/types/Channel';
import { ExtractChannelOutput } from '../encodeable/types/ChannelDef';
import createEncoderClass from '../encodeable/createEncoderClass';

/**
* Define channel names and their types
Expand All @@ -19,6 +18,7 @@ const channelTypes = {
export type ChannelTypes = typeof channelTypes;

/**
* TEMPLATE:
* Helper for defining encoding
*/
type CreateChannelDef<
Expand All @@ -39,28 +39,25 @@ export type Encoding = {
};

/**
* TEMPLATE:
* Can use this to get returned type of a Channel
* example usage: ChannelOutput<'x'>
*/
export type ChannelOutput<ChannelName extends keyof Encoding> = ExtractChannelOutput<
Encoding[ChannelName]
>;

export default class Encoder extends AbstractEncoder<ChannelTypes, Encoding> {
static readonly DEFAULT_ENCODINGS: Encoding = {
export default class Encoder extends createEncoderClass<ChannelTypes, Encoding>({
allChannelOptions: {
fill: { legend: false },
},
channelTypes,
defaultEncoding: {
fill: { value: false },
stroke: { value: '#222' },
strokeDasharray: { value: '' },
strokeWidth: { value: 1 },
x: { field: 'x', type: 'quantitative' },
y: { field: 'y', type: 'quantitative' },
};

static readonly CHANNEL_OPTIONS = {
fill: { legend: false },
};

constructor(spec: PartialSpec<Encoding>) {
super(channelTypes, spec, Encoder.DEFAULT_ENCODINGS, Encoder.CHANNEL_OPTIONS);
}
}
},
}) {}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Value } from 'vega-lite/build/src/channeldef';
import AbstractEncoder from '../encodeable/AbstractEncoder';
import { PartialSpec } from '../encodeable/types/Specification';
import { ChannelTypeToDefMap } from '../encodeable/types/Channel';
import { ExtractChannelOutput } from '../encodeable/types/ChannelDef';
import createEncoderClass from '../encodeable/createEncoderClass';

/**
* Define channel types
Expand All @@ -15,11 +14,12 @@ const channelTypes = {
tooltip: 'Text',
x: 'X',
y: 'Y',
} as const;
} as const; // "as const" is mandatory

export type ChannelTypes = typeof channelTypes;

/**
* TEMPLATE:
* Helper for defining encoding
*/
type CreateChannelDef<
Expand All @@ -41,27 +41,23 @@ export type Encoding = {
};

/**
* TEMPLATE:
* Can use this to get returned type of a Channel
* example usage: ChannelOutput<'x'>
*/
export type ChannelOutput<ChannelName extends keyof Encoding> = ExtractChannelOutput<
Encoding[ChannelName]
>;

export default class Encoder extends AbstractEncoder<ChannelTypes, Encoding> {
static readonly DEFAULT_ENCODINGS: Encoding = {
export default class Encoder extends createEncoderClass<ChannelTypes, Encoding>({
channelTypes,
defaultEncoding: {
fill: { value: '#222' },
group: [],
size: { value: 5 },
stroke: { value: 'none' },
tooltip: [],
x: { field: 'x', type: 'quantitative' },
y: { field: 'y', type: 'quantitative' },
};

static readonly CHANNEL_OPTIONS = {};

constructor(spec: PartialSpec<Encoding>) {
super(channelTypes, spec, Encoder.DEFAULT_ENCODINGS, Encoder.CHANNEL_OPTIONS);
}
}
},
}) {}
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { flatMap } from 'lodash';
import { Value } from 'vega-lite/build/src/channeldef';
import { ChannelOptions, ChannelType, ChannelInput } from './types/Channel';
import { ChannelType, ChannelInput, AllChannelOptions } from './types/Channel';
import { FullSpec, BaseOptions, PartialSpec } from './types/Specification';
import { isFieldDef, isTypedFieldDef, FieldDef, ChannelDef } from './types/ChannelDef';
import { isFieldDef, isTypedFieldDef, ChannelDef } from './types/ChannelDef';
import ChannelEncoder from './ChannelEncoder';
import { Dataset } from './types/Data';
import { Unarray, MayBeArray, isArray, isNotArray } from './types/Base';

type AllChannelEncoders<Encoding extends Record<string, MayBeArray<ChannelDef>>> = {
readonly [k in keyof Encoding]: Encoding[k] extends any[]
? ChannelEncoder<Unarray<Encoding[k]>>[]
: ChannelEncoder<Unarray<Encoding[k]>>
};

export default abstract class AbstractEncoder<
ChannelTypes extends Record<string, ChannelType>,
Encoding extends Record<keyof ChannelTypes, MayBeArray<ChannelDef>>,
Options extends BaseOptions = BaseOptions
> {
readonly channelTypes: ChannelTypes;
readonly spec: FullSpec<Encoding, Options>;
readonly channels: {
readonly [k in keyof Encoding]: Encoding[k] extends any[]
? ChannelEncoder<Unarray<Encoding[k]>>[]
: ChannelEncoder<Unarray<Encoding[k]>>
};
readonly channelTypes: ChannelTypes;
readonly channels: AllChannelEncoders<Encoding>;

readonly legends: {
[key: string]: (keyof Encoding)[];
Expand All @@ -28,28 +30,21 @@ export default abstract class AbstractEncoder<
channelTypes: ChannelTypes,
spec: PartialSpec<Encoding, Options>,
defaultEncoding?: Encoding,
channelOptions: Partial<{ [k in keyof Encoding]: ChannelOptions }> = {},
allChannelOptions: AllChannelOptions<Encoding> = {},
) {
this.channelTypes = channelTypes;
this.spec = this.createFullSpec(spec, defaultEncoding);
const { encoding } = this.spec;

type ChannelName = keyof Encoding;
type Channels = {
readonly [k in keyof Encoding]: Encoding[k] extends any[]
? ChannelEncoder<Unarray<Encoding[k]>>[]
: ChannelEncoder<Unarray<Encoding[k]>>
};

const channelNames = this.getChannelNames();

const tmp: { [k in keyof Encoding]?: MayBeArray<ChannelEncoder<ChannelDef>> } = {};
const channels: { [k in keyof Encoding]?: MayBeArray<ChannelEncoder<ChannelDef>> } = {};

channelNames.forEach(name => {
const channelEncoding = encoding[name];
if (isArray(channelEncoding)) {
const definitions = channelEncoding;
tmp[name] = definitions.map(
channels[name] = definitions.map(
(definition, i) =>
new ChannelEncoder({
definition,
Expand All @@ -59,25 +54,27 @@ export default abstract class AbstractEncoder<
);
} else if (isNotArray(channelEncoding)) {
const definition = channelEncoding;
tmp[name] = new ChannelEncoder({
channels[name] = new ChannelEncoder({
definition,
name,
options: {
...this.spec.options,
...channelOptions[name],
...allChannelOptions[name],
},
type: channelTypes[name],
});
}
});

this.channels = tmp as Channels;
this.channels = channels as AllChannelEncoders<Encoding>;

type ChannelName = keyof Encoding;

// Group the channels that use the same field together
// so they can share the same legend.
this.legends = {};
channelNames
.map((name: ChannelName) => this.channels[name])
.map(name => this.channels[name])
.forEach(c => {
if (isNotArray(c) && c.hasLegend() && isFieldDef(c.definition)) {
const name = c.name as ChannelName;
Expand All @@ -94,7 +91,7 @@ export default abstract class AbstractEncoder<
/**
* subclass can override this
*/
protected createFullSpec(spec: PartialSpec<Encoding, Options>, defaultEncoding?: Encoding) {
createFullSpec(spec: PartialSpec<Encoding, Options>, defaultEncoding?: Encoding) {
if (typeof defaultEncoding === 'undefined') {
return spec as FullSpec<Encoding, Options>;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import AbstractEncoder from './AbstractEncoder';
import { PartialSpec, BaseOptions } from './types/Specification';
import { MayBeArray } from './types/Base';
import { ChannelDef } from './types/ChannelDef';
import { ChannelType, AllChannelOptions } from './types/Channel';

export default function createEncoderClass<
ChannelTypes extends Record<string, ChannelType>,
Encoding extends Record<keyof ChannelTypes, MayBeArray<ChannelDef>>,
Options extends BaseOptions = BaseOptions
>({
channelTypes,
defaultEncoding,
allChannelOptions: allChannelOptions = {},
}: {
channelTypes: ChannelTypes;
defaultEncoding: Encoding;
allChannelOptions?: AllChannelOptions<Encoding>;
}) {
return class Encoder extends AbstractEncoder<ChannelTypes, Encoding, Options> {
static readonly DEFAULT_ENCODING: Encoding = defaultEncoding;

static readonly ALL_CHANNEL_OPTIONS = allChannelOptions;

constructor(spec: PartialSpec<Encoding, Options>) {
super(channelTypes, spec, defaultEncoding, allChannelOptions);
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export interface ChannelOptions {
legend?: boolean;
}

export type AllChannelOptions<Encoding> = Partial<{ [k in keyof Encoding]: ChannelOptions }>;

/**
* Define all channel types and mapping to available definition grammar
*/
Expand All @@ -32,13 +34,3 @@ export interface ChannelTypeToDefMap<Output extends Value = Value> {
}

export type ChannelType = keyof ChannelTypeToDefMap;

// export type ChannelDefFromType<
// T extends keyof ChannelTypeToDefMap,
// Output extends Value
// > = ChannelTypeToDefMap<Output>[T];

// export type EncodingFromChannelsAndOutputs<
// Channels extends ObjectWithKeysFromAndValueType<Outputs, ChannelType>,
// Outputs extends ObjectWithKeysFromAndValueType<Channels, Value>
// > = { [key in keyof Channels]: ChannelDefFromType<Channels[key], Outputs[key]> };
Empty file.

0 comments on commit 52d120a

Please sign in to comment.