From 1c9c3adbb15fc95e2aa003d414bca008db84af6b Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Wed, 5 Jun 2019 14:25:25 -0700 Subject: [PATCH] fix: legend types --- .../src/Line/Encoder.ts | 2 +- .../src/Line/Line.tsx | 2 +- .../src/components/legend/ChartLegend.tsx | 24 ++--- .../src/components/legend/types.ts | 39 ++++---- .../src/encodeable/AbstractEncoder.ts | 98 +++++++++++-------- .../src/encodeable/AxisAgent.ts | 1 - .../src/encodeable/types/Base.ts | 11 +++ .../src/encodeable/types/Channel.ts | 6 +- .../src/encodeable/types/ChannelDef.ts | 2 +- 9 files changed, 103 insertions(+), 82 deletions(-) diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/Line/Encoder.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/Line/Encoder.ts index 0c2a16f07e8e..76f492c767b2 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/Line/Encoder.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/Line/Encoder.ts @@ -1,7 +1,7 @@ import { Value } from 'vega-lite/build/src/channeldef'; import AbstractEncoder from '../encodeable/AbstractEncoder'; import { PartialSpec } from '../encodeable/types/Specification'; -import { ChannelTypeToDefMap, ChannelType } from '../encodeable/types/Channel'; +import { ChannelTypeToDefMap } from '../encodeable/types/Channel'; import { ExtractChannelOutput } from '../encodeable/types/ChannelDef'; /** diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/Line/Line.tsx b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/Line/Line.tsx index 09a29f6132a4..99ac50aeb3a8 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/Line/Line.tsx +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/Line/Line.tsx @@ -53,7 +53,7 @@ export type FormDataProps = { } & PartialSpec; export type HookProps = { - LegendRenderer?: React.ComponentType>; + LegendRenderer?: React.ComponentType>; TooltipRenderer?: React.ComponentType; } & LegendHooks; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/legend/ChartLegend.tsx b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/legend/ChartLegend.tsx index cf32fd18ba3f..7c9495dcb164 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/legend/ChartLegend.tsx +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/legend/ChartLegend.tsx @@ -1,7 +1,6 @@ import React, { CSSProperties, PureComponent } from 'react'; import AbstractEncoder from '../../encodeable/AbstractEncoder'; import { Dataset } from '../../encodeable/types/Data'; -import { ChannelType } from '../../encodeable/types/Channel'; import { LegendItemRendererType, LegendGroupRendererType, @@ -9,7 +8,6 @@ import { LegendItemMarkRendererType, } from './types'; import DefaultLegendGroup from './DefaultLegendGroup'; -import { ChannelDef } from '../../encodeable/types/ChannelDef'; const LEGEND_CONTAINER_STYLE: CSSProperties = { display: 'flex', @@ -21,24 +19,22 @@ const LEGEND_CONTAINER_STYLE: CSSProperties = { position: 'relative', }; -export type Hooks = { - LegendGroupRenderer?: LegendGroupRendererType; - LegendItemRenderer?: LegendItemRendererType; - LegendItemMarkRenderer?: LegendItemMarkRendererType; - LegendItemLabelRenderer?: LegendItemLabelRendererType; +export type Hooks = { + LegendGroupRenderer?: LegendGroupRendererType; + LegendItemRenderer?: LegendItemRendererType; + LegendItemMarkRenderer?: LegendItemMarkRendererType; + LegendItemLabelRenderer?: LegendItemLabelRendererType; }; -export type Props = { +export type Props = { data: Dataset; encoder: Encoder; style?: CSSProperties; -} & Hooks; +} & Hooks ? Encoding : never>; -export default class ChartLegend< - Encoder extends AbstractEncoder, - ChannelTypes extends Record = any, - Encoding extends Record = any -> extends PureComponent, {}> { +export default class ChartLegend> extends PureComponent< + Props +> { render() { const { data, diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/legend/types.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/legend/types.ts index 585728516fa4..0fb7423ca357 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/legend/types.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/components/legend/types.ts @@ -1,40 +1,39 @@ import { Value } from 'vega-lite/build/src/channeldef'; import { CSSProperties } from 'react'; -import { ObjectWithKeysFromAndValueType } from '../../encodeable/types/Base'; import { ChannelInput } from '../../encodeable/types/Channel'; -export type LegendItemInfo = { +export type LegendItemInfo = { field: string; value: ChannelInput; - encodedValues: Partial>; + encodedValues: Partial>; }; -export type LegendItemMarkRendererType = React.ComponentType<{ - item: LegendItemInfo; +export type LegendItemMarkRendererType = React.ComponentType<{ + item: LegendItemInfo; }>; -export type LegendItemLabelRendererType = React.ComponentType<{ - item: LegendItemInfo; +export type LegendItemLabelRendererType = React.ComponentType<{ + item: LegendItemInfo; }>; -export type LegendItemRendererProps = { - item: LegendItemInfo; - MarkRenderer?: LegendItemMarkRendererType; - LabelRenderer?: LegendItemLabelRendererType; +export type LegendItemRendererProps = { + item: LegendItemInfo; + MarkRenderer?: LegendItemMarkRendererType; + LabelRenderer?: LegendItemLabelRendererType; }; -export type LegendItemRendererType = React.ComponentType< - LegendItemRendererProps +export type LegendItemRendererType = React.ComponentType< + LegendItemRendererProps >; -export type LegendGroupRendererProps = { - items: LegendItemInfo[]; - ItemRenderer?: LegendItemRendererType; - ItemMarkRenderer?: LegendItemMarkRendererType; - ItemLabelRenderer?: LegendItemLabelRendererType; +export type LegendGroupRendererProps = { + items: LegendItemInfo[]; + ItemRenderer?: LegendItemRendererType; + ItemMarkRenderer?: LegendItemMarkRendererType; + ItemLabelRenderer?: LegendItemLabelRendererType; style?: CSSProperties; }; -export type LegendGroupRendererType = React.ComponentType< - LegendGroupRendererProps +export type LegendGroupRendererType = React.ComponentType< + LegendGroupRendererProps >; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/AbstractEncoder.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/AbstractEncoder.ts index f8a9cf011521..c28a08a2af4d 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/AbstractEncoder.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/AbstractEncoder.ts @@ -1,19 +1,24 @@ +import { flatMap } from 'lodash'; import { Value } from 'vega-lite/build/src/channeldef'; -import { ObjectWithKeysFromAndValueType } from './types/Base'; import { ChannelOptions, ChannelType, ChannelInput } from './types/Channel'; import { FullSpec, BaseOptions, PartialSpec } from './types/Specification'; import { isFieldDef, isTypedFieldDef, FieldDef, ChannelDef } from './types/ChannelDef'; import ChannelEncoder from './ChannelEncoder'; import { Dataset } from './types/Data'; +import { Unarray, MayBeArray, isArray, isNotArray } from './types/Base'; export default abstract class AbstractEncoder< ChannelTypes extends Record, - Encoding extends Record, + Encoding extends Record>, Options extends BaseOptions = BaseOptions > { readonly channelTypes: ChannelTypes; readonly spec: FullSpec; - readonly channels: { readonly [k in keyof ChannelTypes]: ChannelEncoder }; + readonly channels: { + readonly [k in keyof Encoding]: Encoding[k] extends any[] + ? ChannelEncoder>[] + : ChannelEncoder> + }; readonly commonChannels: { group: ChannelEncoder[]; @@ -21,50 +26,64 @@ export default abstract class AbstractEncoder< }; readonly legends: { - [key: string]: (keyof ChannelTypes)[]; + [key: string]: (keyof Encoding)[]; }; constructor( channelTypes: ChannelTypes, spec: PartialSpec, defaultEncoding?: Encoding, - channelOptions: Partial<{ [k in keyof ChannelTypes]: ChannelOptions }> = {}, + channelOptions: Partial<{ [k in keyof Encoding]: ChannelOptions }> = {}, ) { this.channelTypes = channelTypes; this.spec = this.createFullSpec(spec, defaultEncoding); - - type ChannelName = keyof ChannelTypes; - type Channels = { readonly [k in ChannelName]: ChannelEncoder }; - - const channelNames = Object.keys(this.channelTypes) as ChannelName[]; - const { encoding } = this.spec; - this.channels = channelNames - .map( - (name: ChannelName) => - new ChannelEncoder({ - definition: encoding[name], - name, - options: { - ...this.spec.options, - ...channelOptions[name], - }, - type: channelTypes[name], - }), - ) - .reduce((prev: Partial, curr) => { - const all = prev; - all[curr.name as ChannelName] = curr; - return all; - }, {}) as Channels; + type ChannelName = keyof Encoding; + type Channels = { + readonly [k in keyof Encoding]: Encoding[k] extends any[] + ? ChannelEncoder>[] + : ChannelEncoder> + }; + + const channelNames = this.getChannelNames(); + + const tmp: { [k in keyof Encoding]?: MayBeArray> } = {}; + + channelNames.forEach(name => { + const channelEncoding = encoding[name]; + if (isArray(channelEncoding)) { + const definitions = channelEncoding; + tmp[name] = definitions.map( + (definition, i) => + new ChannelEncoder({ + definition, + name: `${name}[${i}]`, + type: 'Text', + }), + ); + } else if (isNotArray(channelEncoding)) { + const definition = channelEncoding; + tmp[name] = new ChannelEncoder({ + definition, + name, + options: { + ...this.spec.options, + ...channelOptions[name], + }, + type: channelTypes[name], + }); + } + }); + + this.channels = tmp as Channels; this.commonChannels = { group: this.spec.commonEncoding.group.map( (def, i) => new ChannelEncoder({ definition: def, - name: `group${i}`, + name: `group[${i}]`, type: 'Text', }), ), @@ -72,7 +91,7 @@ export default abstract class AbstractEncoder< (def, i) => new ChannelEncoder({ definition: def, - name: `tooltip${i}`, + name: `tooltip[${i}]`, type: 'Text', }), ), @@ -83,9 +102,8 @@ export default abstract class AbstractEncoder< this.legends = {}; channelNames .map((name: ChannelName) => this.channels[name]) - .filter(c => c.hasLegend()) .forEach(c => { - if (isFieldDef(c.definition)) { + if (isNotArray(c) && c.hasLegend() && isFieldDef(c.definition)) { const name = c.name as ChannelName; const { field } = c.definition; if (this.legends[field]) { @@ -130,7 +148,7 @@ export default abstract class AbstractEncoder< } getGroupBys() { - const fields = this.getChannelsAsArray() + const fields = flatMap(this.getChannelsAsArray()) .filter(c => c.isGroupBy()) .map(c => (isFieldDef(c.definition) ? c.definition.field : '')) .filter(field => field !== ''); @@ -144,7 +162,7 @@ export default abstract class AbstractEncoder< const channelNames = this.legends[field]; const channelEncoder = this.channels[channelNames[0]]; - if (isTypedFieldDef(channelEncoder.definition)) { + if (isNotArray(channelEncoder) && isTypedFieldDef(channelEncoder.definition)) { // Only work for nominal channels now // TODO: Add support for numerical scale if (channelEncoder.definition.type === 'nominal') { @@ -155,12 +173,12 @@ export default abstract class AbstractEncoder< value, // eslint-disable-next-line sort-keys encodedValues: channelNames.reduce( - ( - prev: Partial>, - curr, - ) => { + (prev: Partial>, curr) => { const map = prev; - map[curr] = this.channels[curr].encodeValue(value); + const channel = this.channels[curr]; + if (isNotArray(channel)) { + map[curr] = channel.encodeValue(value); + } return map; }, diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/AxisAgent.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/AxisAgent.ts index e03826a87f2a..4e712e86e774 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/AxisAgent.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/AxisAgent.ts @@ -1,6 +1,5 @@ /* eslint-disable no-magic-numbers */ import { CSSProperties } from 'react'; -import { Value } from 'vega-lite/build/src/channeldef'; import { getTextDimension, Margin, Dimension } from '@superset-ui/dimension'; import { CategoricalColorScale } from '@superset-ui/color'; import { extractFormatFromTypeAndFormat } from './parsers/extractFormat'; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/types/Base.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/types/Base.ts index a69ba6e59ee3..fa96c6d666f1 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/types/Base.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/types/Base.ts @@ -1,2 +1,13 @@ // eslint-disable-next-line import/prefer-default-export export type ObjectWithKeysFromAndValueType = { [key in keyof T]: V }; + +export type Unarray = T extends Array ? U : T; +export type MayBeArray = T | T[]; + +export function isArray(maybeArray: T | T[]): maybeArray is T[] { + return Array.isArray(maybeArray); +} + +export function isNotArray(maybeArray: T | T[]): maybeArray is T { + return !Array.isArray(maybeArray); +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/types/Channel.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/types/Channel.ts index f3fc24b9ff06..ab0362313993 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/types/Channel.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/types/Channel.ts @@ -1,6 +1,5 @@ import { Value } from 'vega-lite/build/src/channeldef'; -import { XFieldDef, YFieldDef, ChannelDef, MarkPropChannelDef, TextChannelDef } from './ChannelDef'; -import { ObjectWithKeysFromAndValueType } from './Base'; +import { XFieldDef, YFieldDef, MarkPropChannelDef, TextChannelDef } from './ChannelDef'; export type ChannelInput = number | string | boolean | null | Date | undefined; @@ -13,8 +12,7 @@ export interface ChannelOptions { /** * Define all channel types and mapping to available definition grammar */ -export interface ChannelTypeToDefMap - extends ObjectWithKeysFromAndValueType, ChannelDef> { +export interface ChannelTypeToDefMap { // position on x-axis X: XFieldDef; // position on y-axis diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/types/ChannelDef.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/types/ChannelDef.ts index 9c4d719f41f5..83a8e61a74e7 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/types/ChannelDef.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/superset-ui-plugins/packages/superset-ui-preset-chart-xy/src/encodeable/types/ChannelDef.ts @@ -51,7 +51,7 @@ export type NonValueDef = export type ChannelDef = NonValueDef | ValueDef; -export type ExtractChannelOutput = C extends ChannelDef ? Output : never; +export type ExtractChannelOutput = Def extends ChannelDef ? Output : never; export function isValueDef( channelDef: ChannelDef,