refactor(monorepo): move superset-ui to superset(stage 2) (#17552)

This commit is contained in:
Yongjie Zhao
2021-11-30 08:29:57 +08:00
committed by GitHub
parent bfba4f1689
commit 3c41ff68a4
1315 changed files with 27755 additions and 15167 deletions

View File

@@ -0,0 +1,29 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { EchartsProps } from '../types';
import Echart from '../components/Echart';
export default function EchartsGraph({
height,
width,
echartOptions,
}: EchartsProps) {
return <Echart height={height} width={width} echartOptions={echartOptions} />;
}

View File

@@ -0,0 +1,29 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { buildQueryContext, QueryFormData } from '@superset-ui/core';
export default function buildQuery(formData: QueryFormData) {
return buildQueryContext(formData, {
queryFields: {
id: 'columns',
parent: 'columns',
name: 'columns',
},
});
}

View File

@@ -0,0 +1,30 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { TreeSeriesOption } from 'echarts';
export const DEFAULT_TREE_SERIES_OPTION: TreeSeriesOption = {
label: {
position: 'left',
fontSize: 15,
},
animation: true,
animationDuration: 500,
animationEasing: 'cubicOut',
lineStyle: { color: 'source', width: 1.5 },
};

View File

@@ -0,0 +1,290 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { FeatureFlag, isFeatureEnabled, t } from '@superset-ui/core';
import {
ControlPanelConfig,
sections,
sharedControls,
} from '@superset-ui/chart-controls';
import { DEFAULT_FORM_DATA } from './types';
const requiredEntity = {
...sharedControls.entity,
clearable: false,
};
const optionalEntity = {
...sharedControls.entity,
clearable: true,
validators: [],
};
const controlPanel: ControlPanelConfig = {
controlPanelSections: [
sections.legacyRegularTime,
{
label: t('Query'),
expanded: true,
controlSetRows: [
[
{
name: 'id',
config: {
...requiredEntity,
label: t('Id'),
description: t('Name of the id column'),
},
},
],
[
{
name: 'parent',
config: {
...requiredEntity,
label: t('Parent'),
description: t(
'Name of the column containing the id of the parent node',
),
},
},
],
[
{
name: 'name',
config: {
...optionalEntity,
label: t('Name'),
description: t('Optional name of the data column.'),
},
},
],
[
{
name: 'root_node_id',
config: {
...optionalEntity,
renderTrigger: true,
type: 'TextControl',
label: t('Root node id'),
description: t('Id of root node of the tree.'),
},
},
],
[
{
name: 'metric',
config: {
...optionalEntity,
type: isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP)
? 'DndMetricSelect'
: 'MetricsControl',
label: t('Metric'),
description: t('Metric for node values'),
},
},
],
['adhoc_filters'],
['row_limit'],
],
},
{
label: t('Chart options'),
expanded: true,
controlSetRows: [
[<h1 className="section-header">{t('Layout')}</h1>],
[
{
name: 'layout',
config: {
type: 'RadioButtonControl',
renderTrigger: true,
label: t('Tree layout'),
default: DEFAULT_FORM_DATA.layout,
options: [
['orthogonal', t('Orthogonal')],
['radial', t('Radial')],
],
description: t('Layout type of tree'),
},
},
],
[
{
name: 'orient',
config: {
type: 'RadioButtonControl',
renderTrigger: true,
label: t('Tree orientation'),
default: DEFAULT_FORM_DATA.orient,
options: [
['LR', t('Left to Right')],
['RL', t('Right to Left')],
['TB', t('Top to Bottom')],
['BT', t('Bottom to Top')],
],
description: t('Orientation of tree'),
visibility({ form_data: { layout } }) {
return (layout || DEFAULT_FORM_DATA.layout) === 'orthogonal';
},
},
},
],
[
{
name: 'node_label_position',
config: {
type: 'RadioButtonControl',
renderTrigger: true,
label: t('Node label position'),
default: DEFAULT_FORM_DATA.nodeLabelPosition,
options: [
['left', t('left')],
['top', t('top')],
['right', t('right')],
['bottom', t('bottom')],
],
description: t('Position of intermidiate node label on tree'),
},
},
],
[
{
name: 'child_label_position',
config: {
type: 'RadioButtonControl',
renderTrigger: true,
label: t('Child label position'),
default: DEFAULT_FORM_DATA.childLabelPosition,
options: [
['left', t('left')],
['top', t('top')],
['right', t('right')],
['bottom', t('bottom')],
],
description: t('Position of child node label on tree'),
},
},
],
[
{
name: 'emphasis',
config: {
type: 'RadioButtonControl',
renderTrigger: true,
label: t('Emphasis'),
default: DEFAULT_FORM_DATA.emphasis,
options: [
['ancestor', t('ancestor')],
['descendant', t('descendant')],
],
description: t('Which relatives to highlight on hover'),
visibility({ form_data: { layout } }) {
return (layout || DEFAULT_FORM_DATA.layout) === 'orthogonal';
},
},
},
],
[
{
name: 'symbol',
config: {
type: 'SelectControl',
renderTrigger: true,
label: t('Symbol'),
default: DEFAULT_FORM_DATA.symbol,
options: [
{
label: t('Empty circle'),
value: 'emptyCircle',
},
{
label: t('Circle'),
value: 'circle',
},
{
label: t('Rectangle'),
value: 'rect',
},
{
label: t('Triangle'),
value: 'triangle',
},
{
label: t('Diamond'),
value: 'diamond',
},
{
label: t('Pin'),
value: 'pin',
},
{
label: t('Arrow'),
value: 'arrow',
},
{
label: t('None'),
value: 'none',
},
],
description: t('Layout type of tree'),
},
},
],
[
{
name: 'symbolSize',
config: {
type: 'SliderControl',
label: t('Symbol size'),
renderTrigger: true,
min: 5,
max: 30,
step: 2,
default: DEFAULT_FORM_DATA.symbolSize,
description: t('Size of edge symbols'),
},
},
],
[
{
name: 'roam',
config: {
type: 'SelectControl',
label: t('Enable graph roaming'),
renderTrigger: true,
default: DEFAULT_FORM_DATA.roam,
choices: [
[false, t('Disabled')],
['scale', t('Scale only')],
['move', t('Move only')],
[true, t('Scale and Move')],
],
description: t(
'Whether to enable changing graph position and scaling.',
),
},
},
],
],
},
],
};
export default controlPanel;

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -0,0 +1,52 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
import controlPanel from './controlPanel';
import transformProps from './transformProps';
import thumbnail from './images/thumbnail.png';
import example from './images/tree.png';
import buildQuery from './buildQuery';
export default class EchartsTreeChartPlugin extends ChartPlugin {
constructor() {
super({
buildQuery,
controlPanel,
loadChart: () => import('./EchartsTree'),
metadata: new ChartMetadata({
category: t('Part of a Whole'),
credits: ['https://echarts.apache.org'],
description: t(
'Visualize multiple levels of hierarchy using a familiar tree-like structure.',
),
exampleGallery: [{ url: example }],
name: t('Tree Chart'),
tags: [
t('Categorical'),
t('ECharts'),
t('Multi-Levels'),
t('Relational'),
t('Structural'),
],
thumbnail,
}),
transformProps,
});
}
}

View File

@@ -0,0 +1,219 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ChartProps, getMetricLabel, DataRecordValue } from '@superset-ui/core';
import { EChartsCoreOption, TreeSeriesOption } from 'echarts';
import {
TreeSeriesCallbackDataParams,
TreeSeriesNodeItemOption,
} from 'echarts/types/src/chart/tree/TreeSeries';
import { OptionName } from 'echarts/types/src/util/types';
import {
EchartsTreeFormData,
DEFAULT_FORM_DATA as DEFAULT_GRAPH_FORM_DATA,
TreeDataRecord,
} from './types';
import { DEFAULT_TREE_SERIES_OPTION } from './constants';
import { EchartsProps } from '../types';
export function formatTooltip({
params,
metricLabel,
}: {
params: TreeSeriesCallbackDataParams;
metricLabel: string;
}): string {
const { value, treeAncestors } = params;
const treePath = (treeAncestors ?? [])
.map(pathInfo => pathInfo?.name || '')
.filter(path => path !== '');
return [
`<div>${treePath.join(' ▸ ')}</div>`,
value ? `${metricLabel}: ${value}` : '',
].join('');
}
export default function transformProps(chartProps: ChartProps): EchartsProps {
const { width, height, formData, queriesData } = chartProps;
const data: TreeDataRecord[] = queriesData[0].data || [];
const {
id,
parent,
name,
metric = '',
rootNodeId,
layout,
orient,
symbol,
symbolSize,
roam,
nodeLabelPosition,
childLabelPosition,
emphasis,
}: EchartsTreeFormData = { ...DEFAULT_GRAPH_FORM_DATA, ...formData };
const metricLabel = getMetricLabel(metric);
const nameColumn = name || id;
function findNodeName(rootNodeId: DataRecordValue): OptionName {
let nodeName: DataRecordValue = '';
data.some(node => {
if (node[id]!.toString() === rootNodeId) {
nodeName = node[nameColumn];
return true;
}
return false;
});
return nodeName;
}
function getTotalChildren(tree: TreeSeriesNodeItemOption) {
let totalChildren = 0;
function traverse(tree: TreeSeriesNodeItemOption) {
tree.children!.forEach(node => {
traverse(node);
});
totalChildren += 1;
}
traverse(tree);
return totalChildren;
}
function createTree(rootNodeId: DataRecordValue): TreeSeriesNodeItemOption {
const rootNodeName = findNodeName(rootNodeId);
const tree: TreeSeriesNodeItemOption = { name: rootNodeName, children: [] };
const children: TreeSeriesNodeItemOption[][] = [];
const indexMap: { [name: string]: number } = {};
if (!rootNodeName) {
return tree;
}
// index indexMap with node ids
for (let i = 0; i < data.length; i += 1) {
const nodeId = data[i][id] as number;
indexMap[nodeId] = i;
children[i] = [];
}
// generate tree
for (let i = 0; i < data.length; i += 1) {
const node = data[i];
if (node[parent]?.toString() === rootNodeId) {
tree.children?.push({
name: node[nameColumn],
children: children[i],
value: node[metricLabel],
});
} else {
const parentId = node[parent];
if (data[indexMap[parentId]]) {
const parentIndex = indexMap[parentId];
children[parentIndex].push({
name: node[nameColumn],
children: children[i],
value: node[metricLabel],
});
}
}
}
return tree;
}
let finalTree = {};
if (rootNodeId) {
finalTree = createTree(rootNodeId);
} else {
/*
to select root node,
1.find parent nodes with only 1 child.
2.build tree for each such child nodes as root
3.select tree with most children
*/
// create map of parent:children
const parentChildMap: { [name: string]: { [name: string]: any } } = {};
data.forEach(node => {
const parentId = node[parent] as string;
if (parentId in parentChildMap) {
parentChildMap[parentId].push({ id: node[id] });
} else {
parentChildMap[parentId] = [{ id: node[id] }];
}
});
// for each parent node which has only 1 child,find tree and select node with max number of children.
let maxChildren = 0;
Object.keys(parentChildMap).forEach(key => {
if (parentChildMap[key].length === 1) {
const tree = createTree(parentChildMap[key][0].id);
const totalChildren = getTotalChildren(tree);
if (totalChildren > maxChildren) {
maxChildren = totalChildren;
finalTree = tree;
}
}
});
}
const series: TreeSeriesOption[] = [
{
type: 'tree',
data: [finalTree],
label: {
...DEFAULT_TREE_SERIES_OPTION.label,
position: nodeLabelPosition,
},
emphasis: { focus: emphasis },
animation: DEFAULT_TREE_SERIES_OPTION.animation,
layout,
orient,
symbol,
roam,
symbolSize,
lineStyle: DEFAULT_TREE_SERIES_OPTION.lineStyle,
select: DEFAULT_TREE_SERIES_OPTION.select,
leaves: { label: { position: childLabelPosition } },
},
];
const echartOptions: EChartsCoreOption = {
animationDuration: DEFAULT_TREE_SERIES_OPTION.animationDuration,
animationEasing: DEFAULT_TREE_SERIES_OPTION.animationEasing,
series,
tooltip: {
trigger: 'item',
triggerOn: 'mousemove',
formatter: (params: any) =>
formatTooltip({
params,
metricLabel,
}),
},
};
return {
width,
height,
echartOptions,
};
}

View File

@@ -0,0 +1,55 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { TreeSeriesNodeItemOption } from 'echarts/types/src/chart/tree/TreeSeries';
export type EchartsTreeFormData = {
id: string;
parent: string;
name: string;
rootNodeId?: string | number;
orient: 'LR' | 'RL' | 'TB' | 'BT';
symbol: string;
symbolSize: number;
colorScheme?: string;
metric?: string;
layout: 'orthogonal' | 'radial';
roam: boolean | 'scale' | 'move';
nodeLabelPosition: 'top' | 'bottom' | 'left' | 'right';
childLabelPosition: 'top' | 'bottom' | 'left' | 'right';
emphasis: 'none' | 'ancestor' | 'descendant';
};
export const DEFAULT_FORM_DATA: EchartsTreeFormData = {
id: '',
parent: '',
name: '',
rootNodeId: '',
layout: 'orthogonal',
orient: 'LR',
symbol: 'emptyCircle',
symbolSize: 7,
roam: true,
nodeLabelPosition: 'left',
childLabelPosition: 'bottom',
emphasis: 'descendant',
};
export type TreeDataRecord = Record<string, string | number> & {
children: TreeSeriesNodeItemOption[];
};