Skip to content
This repository has been archived by the owner on Dec 10, 2021. It is now read-only.

Commit

Permalink
feat(encodable): fill missing fields in user-specified channel defini…
Browse files Browse the repository at this point in the history
…tion (#222)

* feat: fill missing fields in user-specified channel definition

* fix: type

* test: add unit tests

* fix: lint

* test: add more unit tests

* test: add unit tests

* test: fix unit tests

* fix: unit tests

* refactor: change order of parameters

* fix: type annotation

* fix: type annotation

* feat: add generic support

* refactor: reorder params

* fix: rename variables
  • Loading branch information
kristw authored Sep 10, 2019
1 parent d02bb82 commit 4a38903
Show file tree
Hide file tree
Showing 24 changed files with 743 additions and 80 deletions.
73 changes: 73 additions & 0 deletions packages/superset-ui-encodable/src/fillers/completeAxisConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* eslint-disable no-magic-numbers */
import isEnabled from '../utils/isEnabled';
import { isTypedFieldDef } from '../typeGuards/ChannelDef';
import { ChannelDef, PositionFieldDef } from '../types/ChannelDef';
import { ChannelType } from '../types/Channel';
import { isXOrY, isX } from '../typeGuards/Channel';
import { RequiredSome } from '../types/Base';
import { AxisConfig, LabelOverlapStrategy } from '../types/Axis';
import expandLabelOverlapStrategy from './expandLabelOverlapStrategy';

function isChannelDefWithAxisSupport(
channelType: ChannelType,
channelDef: ChannelDef,
): channelDef is PositionFieldDef {
return isTypedFieldDef(channelDef) && isXOrY(channelType);
}

export type CompleteAxisConfig =
| false
| RequiredSome<
Omit<AxisConfig, 'labelOverlap'>,
| 'labelAngle'
| 'labelFlush'
| 'labelPadding'
| 'orient'
| 'tickCount'
| 'ticks'
| 'title'
| 'titlePadding'
> & {
labelOverlap: LabelOverlapStrategy;
};

export default function completeAxisConfig(
channelType: ChannelType,
channelDef: ChannelDef,
): CompleteAxisConfig {
if (isChannelDefWithAxisSupport(channelType, channelDef) && isEnabled(channelDef.axis)) {
const axis =
channelDef.axis === true || typeof channelDef.axis === 'undefined' ? {} : channelDef.axis;

const isXChannel = isX(channelType);

const {
format = channelDef.format,
labelAngle = 0,
labelFlush = true,
labelOverlap,
labelPadding = 4,
orient = isXChannel ? 'bottom' : 'left',
tickCount = 5,
ticks = true,
title = channelDef.title!,
titlePadding = 4,
} = axis;

return {
...axis,
format,
labelAngle,
labelFlush,
labelOverlap: expandLabelOverlapStrategy(channelType, labelOverlap),
labelPadding,
orient,
tickCount,
ticks,
title,
titlePadding,
};
}

return false as const;
}
32 changes: 32 additions & 0 deletions packages/superset-ui-encodable/src/fillers/completeChannelDef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ChannelDef } from '../types/ChannelDef';
import { ChannelType } from '../types/Channel';
import { isFieldDef } from '../typeGuards/ChannelDef';
import completeAxisConfig, { CompleteAxisConfig } from './completeAxisConfig';
import completeScaleConfig, { CompleteScaleConfig } from './completeScaleConfig';
import { Value } from '../types/VegaLite';

type CompleteChannelDef<Output extends Value = Value> = Omit<
ChannelDef,
'title' | 'axis' | 'scale'
> & {
axis: CompleteAxisConfig;
scale: CompleteScaleConfig<Output>;
title: string;
};

export default function completeChannelDef<Output extends Value = Value>(
channelType: ChannelType,
channelDef: ChannelDef<Output>,
): CompleteChannelDef<Output> {
// Fill top-level properties
const copy = {
...channelDef,
title: isFieldDef(channelDef) ? channelDef.title || channelDef.field : '',
};

return {
...copy,
axis: completeAxisConfig(channelType, copy),
scale: completeScaleConfig(channelType, copy),
};
}
41 changes: 41 additions & 0 deletions packages/superset-ui-encodable/src/fillers/completeScaleConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { isTypedFieldDef } from '../typeGuards/ChannelDef';
import inferScaleType from './inferScaleType';
import { isContinuousScaleConfig, isScaleConfigWithZero } from '../typeGuards/Scale';
import { ScaleConfig } from '../types/Scale';
import { ChannelDef } from '../types/ChannelDef';
import isEnabled from '../utils/isEnabled';
import { ChannelType } from '../types/Channel';
import { Value } from '../types/VegaLite';

export type CompleteScaleConfig<Output extends Value = Value> = false | ScaleConfig<Output>;

export default function completeScaleConfig<Output extends Value = Value>(
channelType: ChannelType,
channelDef: ChannelDef<Output>,
): CompleteScaleConfig<Output> {
if (isTypedFieldDef(channelDef) && isEnabled(channelDef.scale)) {
const { scale = {}, type, bin } = channelDef;
const { type: scaleType = inferScaleType(channelType, type, bin) } = scale;

if (typeof scaleType === 'undefined') {
return false;
}

const filledScale = { ...scale, type: scaleType } as ScaleConfig<Output>;
if (isContinuousScaleConfig(filledScale)) {
if (typeof filledScale.nice === 'undefined') {
filledScale.nice = true;
}
if (typeof filledScale.clamp === 'undefined') {
filledScale.clamp = true;
}
}
if (isScaleConfigWithZero(filledScale) && typeof filledScale.zero === 'undefined') {
filledScale.zero = true;
}

return filledScale;
}

return false as const;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { LabelOverlapStrategy, LabelOverlapType } from '../types/Axis';
import { ChannelType } from '../types/Channel';
import { isX } from '../typeGuards/Channel';

const STRATEGY_FLAT = { strategy: 'flat' } as const;
const STRATEGY_ROTATE = { labelAngle: 40, strategy: 'rotate' } as const;

export default function expandLabelOverlapStrategy(
channelType: ChannelType,
labelOverlap: LabelOverlapType = 'auto',
): LabelOverlapStrategy {
let output: LabelOverlapStrategy;
switch (labelOverlap) {
case 'flat':
output = STRATEGY_FLAT;
break;
case 'rotate':
output = STRATEGY_ROTATE;
break;
case 'auto':
output = isX(channelType) ? STRATEGY_ROTATE : STRATEGY_FLAT;
break;
default:
output = labelOverlap;
break;
}

return { ...output };
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { Type, ScaleType } from '../../types/VegaLite';
import { ChannelType } from '../../types/Channel';
import { Type, ScaleType } from '../types/VegaLite';
import { ChannelType } from '../types/Channel';

/**
* Sometimes scale type is not specified but can be inferred
* from other fields.
* See https://vega.github.io/vega-lite/docs/scale.html
* @param channelType type of the channel
* @param fieldType type of the field
* @param isBinned is value binned
* @param bin is value binned
*/
// eslint-disable-next-line complexity
export default function inferScaleType(
channelType: ChannelType,
fieldType?: Type,
isBinned: boolean = false,
bin: boolean = false,
): ScaleType | undefined {
if (fieldType === 'nominal' || fieldType === 'ordinal') {
switch (channelType) {
Expand Down Expand Up @@ -42,7 +42,7 @@ export default function inferScaleType(
case 'Numeric':
return ScaleType.LINEAR;
case 'Color':
return isBinned ? ScaleType.BIN_ORDINAL : ScaleType.LINEAR;
return bin ? ScaleType.BIN_ORDINAL : ScaleType.LINEAR;
default:
}
} else if (fieldType === 'temporal') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default function applyClamp<Output extends Value>(
config: ScaleConfig<Output>,
scale: D3Scale<Output>,
) {
if ('clamp' in config && typeof config.clamp !== 'undefined' && 'clamp' in scale) {
if ('clamp' in config && config.clamp === true && 'clamp' in scale) {
scale.clamp(config.clamp);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function applyRound<Output extends Value>(
config: ScaleConfig<Output>,
scale: D3Scale<Output>,
) {
if ('round' in config && typeof config.round !== 'undefined') {
if ('round' in config && config.round === true) {
const roundableScale = scale as
| ContinuousD3Scale<number>
| ScalePoint<HasToString>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default function applyZero<Output extends Value>(
config: ScaleConfig<Output>,
scale: D3Scale<Output>,
) {
if ('zero' in config && typeof config.zero !== 'undefined') {
if ('zero' in config && config.zero === true) {
const [min, max] = (scale as ContinuousD3Scale<Output>).domain() as number[];
scale.domain([Math.min(0, min), Math.max(0, max)]);
}
Expand Down
13 changes: 13 additions & 0 deletions packages/superset-ui-encodable/src/typeGuards/Channel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ChannelType } from '../types/Channel';

export function isX(channelType: ChannelType): channelType is 'X' | 'XBand' {
return channelType === 'X' || channelType === 'XBand';
}

export function isY(channelType: ChannelType): channelType is 'Y' | 'YBand' {
return channelType === 'Y' || channelType === 'YBand';
}

export function isXOrY(channelType: ChannelType): channelType is 'X' | 'XBand' | 'Y' | 'YBand' {
return isX(channelType) || isY(channelType);
}
4 changes: 2 additions & 2 deletions packages/superset-ui-encodable/src/typeGuards/ChannelDef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ export function isTypedFieldDef<Output extends Value>(
export function isScaleFieldDef<Output extends Value>(
channelDef: ChannelDef<Output>,
): channelDef is ScaleFieldDef<Output> {
return channelDef && 'scale' in channelDef;
return isTypedFieldDef(channelDef) && 'scale' in channelDef;
}

export function isPositionFieldDef<Output extends Value>(
channelDef: ChannelDef<Output>,
): channelDef is PositionFieldDef<Output> {
return channelDef && 'axis' in channelDef;
return isTypedFieldDef(channelDef) && 'axis' in channelDef;
}
38 changes: 36 additions & 2 deletions packages/superset-ui-encodable/src/typeGuards/Scale.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,42 @@
import { CategoricalColorScale } from '@superset-ui/color';
import { ScaleTime } from 'd3-scale';
import { D3Scale } from '../types/Scale';
import {
D3Scale,
ScaleConfig,
LinearScaleConfig,
LogScaleConfig,
PowScaleConfig,
SqrtScaleConfig,
SymlogScaleConfig,
TimeScaleConfig,
UtcScaleConfig,
} from '../types/Scale';
import { Value, ScaleType } from '../types/VegaLite';
import { timeScaleTypesSet } from '../parsers/scale/scaleCategories';
import { timeScaleTypesSet, continuousScaleTypesSet } from '../parsers/scale/scaleCategories';
import isPropertySupportedByScaleType from '../parsers/scale/isPropertySupportedByScaleType';

export function isContinuousScaleConfig<Output extends Value = Value>(
config: ScaleConfig,
): config is
| LinearScaleConfig<Output>
| LogScaleConfig<Output>
| PowScaleConfig<Output>
| SqrtScaleConfig<Output>
| SymlogScaleConfig<Output>
| TimeScaleConfig<Output>
| UtcScaleConfig<Output> {
return continuousScaleTypesSet.has(config.type);
}

export function isScaleConfigWithZero<Output extends Value = Value>(
config: ScaleConfig,
): config is
| LinearScaleConfig<Output>
| PowScaleConfig<Output>
| SqrtScaleConfig<Output>
| SymlogScaleConfig<Output> {
return isPropertySupportedByScaleType('zero', config.type);
}

export function isCategoricalColorScale<Output extends Value = Value>(
scale: D3Scale<Output> | CategoricalColorScale,
Expand Down
Loading

0 comments on commit 4a38903

Please sign in to comment.