Skip to content
This repository was archived by the owner on Jun 25, 2020. It is now read-only.

feat: migrate scatterplot to use encodable #421

Merged
merged 2 commits into from
Apr 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,50 +1,50 @@
import React from 'react';
import { TooltipFrame, TooltipTable } from '@superset-ui/chart-composition';
import { isFieldDef } from '../encodeable/types/ChannelDef';
import { isFieldDef } from 'encodable/lib/typeGuards/ChannelDef';
import { TooltipProps } from './ScatterPlot';

export default function DefaultTooltipRenderer({ datum, encoder }: TooltipProps) {
const { channels } = encoder;
const { x, y, size, fill, stroke } = channels;

const tooltipRows = [
{ key: 'x', keyColumn: x.getTitle(), valueColumn: x.format(datum.data) },
{ key: 'y', keyColumn: y.getTitle(), valueColumn: y.format(datum.data) },
{ key: 'x', keyColumn: x.getTitle(), valueColumn: x.formatDatum(datum.data) },
{ key: 'y', keyColumn: y.getTitle(), valueColumn: y.formatDatum(datum.data) },
];

if (isFieldDef(fill.definition)) {
tooltipRows.push({
key: 'fill',
keyColumn: fill.getTitle(),
valueColumn: fill.format(datum.data),
valueColumn: fill.formatDatum(datum.data),
});
}
if (isFieldDef(stroke.definition)) {
tooltipRows.push({
key: 'stroke',
keyColumn: stroke.getTitle(),
valueColumn: stroke.format(datum.data),
valueColumn: stroke.formatDatum(datum.data),
});
}
if (isFieldDef(size.definition)) {
tooltipRows.push({
key: 'size',
keyColumn: size.getTitle(),
valueColumn: size.format(datum.data),
valueColumn: size.formatDatum(datum.data),
});
}
channels.group.forEach(g => {
tooltipRows.push({
key: `${g.name}`,
keyColumn: g.getTitle(),
valueColumn: g.format(datum.data),
valueColumn: g.formatDatum(datum.data),
});
});
channels.tooltip.forEach(g => {
tooltipRows.push({
key: `${g.name}`,
keyColumn: g.getTitle(),
valueColumn: g.format(datum.data),
valueColumn: g.formatDatum(datum.data),
});
});

Expand Down
82 changes: 29 additions & 53 deletions packages/superset-ui-preset-chart-xy/src/ScatterPlot/Encoder.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,39 @@
import { Value } from 'vega-lite/build/src/channeldef';
import { ChannelTypeToDefMap } from '../encodeable/types/Channel';
import { ExtractChannelOutput } from '../encodeable/types/ChannelDef';
import createEncoderClass from '../encodeable/createEncoderClass';
import { createEncoderFactory, Encoder } from 'encodable';
import { DeriveEncoding, DeriveChannelOutputs } from 'encodable/lib/types/Encoding';

/**
* Define channel types
*/
const channelTypes = {
fill: 'Color',
group: 'Text',
size: 'Numeric',
stroke: 'Color',
tooltip: 'Text',
x: 'X',
y: 'Y',
} as const; // "as const" is mandatory

export type ChannelTypes = typeof channelTypes;

/**
* TEMPLATE:
* Helper for defining encoding
*/
type CreateChannelDef<
ChannelName extends keyof ChannelTypes,
Output extends Value
> = ChannelTypeToDefMap<Output>[ChannelTypes[ChannelName]];

/**
* Encoding definition
*/
export type Encoding = {
fill: CreateChannelDef<'fill', string>;
group: CreateChannelDef<'group', string>[];
size: CreateChannelDef<'size', number>;
stroke: CreateChannelDef<'stroke', string>;
tooltip: CreateChannelDef<'tooltip', string>[];
x: CreateChannelDef<'x', number>;
y: CreateChannelDef<'y', number>;
export type ScatterPlotEncodingConfig = {
x: ['X', number];
y: ['Y', number];
fill: ['Color', string];
group: ['Category', string, 'multiple'];
size: ['Numeric', number];
stroke: ['Color', string];
tooltip: ['Text', string, 'multiple'];
};

/**
* 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 createEncoderClass<ChannelTypes, Encoding>({
channelTypes,
export const scatterPlotEncoderFactory = createEncoderFactory<ScatterPlotEncodingConfig>({
channelTypes: {
x: 'X',
y: 'Y',
fill: 'Color',
group: 'Category',
size: 'Numeric',
stroke: 'Color',
tooltip: 'Text',
},
defaultEncoding: {
x: { field: 'x', type: 'quantitative' },
y: { field: 'y', type: 'quantitative' },
fill: { value: '#222' },
group: [],
size: { value: 5 },
stroke: { value: 'none' },
tooltip: [],
x: { field: 'x', type: 'quantitative' },
y: { field: 'y', type: 'quantitative' },
},
}) {}
});

export type ScatterPlotEncoding = DeriveEncoding<ScatterPlotEncodingConfig>;

export type ScatterPlotEncoder = Encoder<ScatterPlotEncodingConfig>;

export type ScatterPlotChannelOutputs = DeriveChannelOutputs<ScatterPlotEncodingConfig>;
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,56 @@ import { XYChart, PointSeries } from '@data-ui/xy-chart';
import { chartTheme, ChartTheme } from '@data-ui/theme';
import { Margin, Dimension } from '@superset-ui/dimension';
import { WithLegend } from '@superset-ui/chart-composition';
import Encoder, { Encoding, ChannelOutput } from './Encoder';
import { Dataset, PlainObject } from '../encodeable/types/Data';
import { PartialSpec } from '../encodeable/types/Specification';
import { isFieldDef } from 'encodable/lib/typeGuards/ChannelDef';
import { Dataset, PlainObject } from 'encodable/lib/types/Data';
import {
scatterPlotEncoderFactory,
ScatterPlotEncoder,
ScatterPlotChannelOutputs,
ScatterPlotEncodingConfig,
ScatterPlotEncoding,
} from './Encoder';
import createMarginSelector, { DEFAULT_MARGIN } from '../utils2/createMarginSelector';
import DefaultTooltipRenderer from './DefaultTooltipRenderer';
import convertScaleToDataUIScale from '../utils/convertScaleToDataUIScaleShape';
import { isScaleFieldDef } from '../encodeable/types/ChannelDef';
import createXYChartLayoutWithTheme from '../utils/createXYChartLayoutWithTheme';
import createEncoderSelector from '../encodeable/createEncoderSelector';
import createRenderLegend from '../components/legend/createRenderLegend';
import { LegendHooks } from '../components/legend/types';
import createXYChartLayoutWithTheme from '../utils2/createXYChartLayoutWithTheme';
import createRenderLegend from '../components/legend2/createRenderLegend';
import { LegendHooks } from '../components/legend2/types';

export interface TooltipProps {
datum: EncodedPoint;
encoder: Encoder;
encoder: ScatterPlotEncoder;
}

const defaultProps = {
className: '',
margin: DEFAULT_MARGIN,
encoding: {},
theme: chartTheme,
TooltipRenderer: DefaultTooltipRenderer,
} as const;

export type HookProps = {
TooltipRenderer?: React.ComponentType<TooltipProps>;
} & LegendHooks<Encoder>;
} & LegendHooks<ScatterPlotEncodingConfig>;

type Props = {
className?: string;
width: string | number;
height: string | number;
margin?: Margin;
data: Dataset;
encoding?: Partial<ScatterPlotEncoding>;
theme?: ChartTheme;
} & PartialSpec<Encoding> &
HookProps &
} & HookProps &
Readonly<typeof defaultProps>;

export interface EncodedPoint {
x: ChannelOutput<'x'>;
y: ChannelOutput<'y'>;
size: ChannelOutput<'size'>;
fill: ChannelOutput<'fill'>;
stroke: ChannelOutput<'stroke'>;
group: ChannelOutput<'group'>[];
tooltip: ChannelOutput<'tooltip'>[];
export type EncodedPoint = ScatterPlotChannelOutputs & {
data: PlainObject;
}
};

export default class ScatterPlot extends PureComponent<Props> {
private createEncoder = createEncoderSelector(Encoder);
private createEncoder = scatterPlotEncoderFactory.createSelector();

private createMargin = createMarginSelector();

Expand All @@ -68,34 +66,18 @@ export default class ScatterPlot extends PureComponent<Props> {

renderChart(dim: Dimension) {
const { width, height } = dim;
const { data, margin, theme, TooltipRenderer } = this.props;
const encoder = this.createEncoder(this.props);
const { data, margin, theme, TooltipRenderer, encoding } = this.props;
const encoder = this.createEncoder(encoding);
const { channels } = encoder;

if (typeof channels.x.scale !== 'undefined') {
const xDomain = channels.x.getDomain(data);
channels.x.scale.setDomain(xDomain);
}
if (typeof channels.y.scale !== 'undefined') {
const yDomain = channels.y.getDomain(data);
channels.y.scale.setDomain(yDomain);
}
if (
isScaleFieldDef(channels.size.definition) &&
channels.size.definition.type === 'quantitative'
) {
const domain = channels.size.getDomain(data) as number[];
const [min, max] = domain;
const adjustedDomain = [Math.min(min || 0, 0), Math.max(max || 1, 1)];
channels.size.scale!.setDomain(adjustedDomain);
}
encoder.setDomainFromDataset(data);

const encodedData = data.map(d => ({
x: channels.x.get(d),
y: channels.y.get(d),
size: channels.size.encode(d),
fill: channels.fill.encode(d),
stroke: channels.stroke.encode(d),
x: channels.x.encodeDatum(d),
y: channels.y.encodeDatum(d),
size: channels.size.encodeDatum(d),
fill: channels.fill.encodeDatum(d),
stroke: channels.stroke.encodeDatum(d),
data: d,
}));

Expand All @@ -119,13 +101,13 @@ export default class ScatterPlot extends PureComponent<Props> {
<TooltipRenderer datum={datum} encoder={encoder} />
)}
theme={theme}
xScale={convertScaleToDataUIScale(channels.x.scale!.config)}
yScale={convertScaleToDataUIScale(channels.y.scale!.config)}
xScale={convertScaleToDataUIScale(channels.x.definition.scale as any)}
yScale={convertScaleToDataUIScale(channels.y.definition.scale as any)}
>
{layout.renderXAxis()}
{layout.renderYAxis()}
<PointSeries
key={channels.x.definition.field}
key={isFieldDef(channels.x.definition) ? channels.x.definition.field : ''}
data={encodedData}
fill={(d: EncodedPoint) => d.fill}
fillOpacity={0.5}
Expand All @@ -137,9 +119,9 @@ export default class ScatterPlot extends PureComponent<Props> {
}

render() {
const { className, data, width, height } = this.props;
const { className, data, width, height, encoding } = this.props;

const encoder = this.createEncoder(this.props);
const encoder = this.createEncoder(encoding);

return (
<WithLegend
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ export default function DefaultLegendItem<Config extends EncodingConfig>({
fill={
// @ts-ignore
(item.output.color ??
// @ts-ignore
item.output.stroke ??
// @ts-ignore
item.output.fill ??
// @ts-ignore
item.output.stroke ??
'#ccc') as string
}
stroke={
Expand Down