feat: combo interface

This commit is contained in:
baizn 2020-03-23 12:26:25 +08:00 committed by Yanyan Wang
parent 9e3d5bca83
commit 71076b7355
10 changed files with 454 additions and 56 deletions

View File

@ -3,6 +3,7 @@ export default {
rootContainerClassName: 'root-container',
nodeContainerClassName: 'node-container',
edgeContainerClassName: 'edge-container',
comboContainerClassName: 'combo-container',
customGroupContainerClassName: 'custom-group-container',
delegateContainerClassName: 'delegate-container',
defaultShapeFillColor: '#C6E5FF',

View File

@ -1,7 +1,6 @@
import each from '@antv/util/lib/each';
import isString from '@antv/util/lib/is-string';
import { IStates } from '../../interface/graph';
import { Item } from '../../types';
import { Item, IStates } from '../../types';
import Graph from '../graph';
import { INode } from '../../interface/item';

View File

@ -17,9 +17,9 @@ import {
IGraph,
IModeOption,
IModeType,
IStates,
IStates
} from '../interface/graph';
import { IEdge, INode } from '../interface/item';
import { IEdge, INode, ICombo } from '../interface/item';
import {
EdgeConfig,
GraphData,
@ -32,6 +32,7 @@ import {
NodeMap,
Padding,
TreeGraphData,
ComboConfig
} from '../types';
import { getAllNodeInGroups } from '../util/group';
import { move, translate } from '../util/math';
@ -69,6 +70,8 @@ export interface PrivateGraphOption extends GraphOptions {
groups: GroupConfig[];
combos: ComboConfig[];
itemMap: NodeMap;
callback: () => void;
@ -194,6 +197,11 @@ export default class Graph extends EventEmitter implements IGraph {
className: Global.nodeContainerClassName,
});
const comboGroup: IGroup = group.addGroup({
id: `${id}-combo`,
className: Global.comboContainerClassName
})
const delegateGroup: IGroup = group.addGroup({
id: `${id}-delegate`,
className: Global.delegateContainerClassName,
@ -207,7 +215,7 @@ export default class Graph extends EventEmitter implements IGraph {
customGroup.toBack();
this.set({ nodeGroup, edgeGroup, customGroup, delegateGroup });
this.set({ nodeGroup, edgeGroup, customGroup, delegateGroup, comboGroup });
}
this.set('group', group);
}
@ -372,6 +380,8 @@ export default class Graph extends EventEmitter implements IGraph {
* group
*/
groups: [],
combos: [],
/**
* group样式
*/
@ -455,6 +465,16 @@ export default class Graph extends EventEmitter implements IGraph {
}
}
/**
* combo
* @param comboFn
*/
public combo(comboFn: (config: ComboConfig) => Partial<ComboConfig>): void {
if (typeof comboFn === 'function') {
this.set('comboMapper', comboFn)
}
}
/**
* ID
* @param id ID
@ -1162,6 +1182,22 @@ export default class Graph extends EventEmitter implements IGraph {
return this.get('edges');
}
/**
* combo
*/
public getCombos(): ICombo[] {
return this.get('combos')
}
// TODO 待实现getComboNodes方法
/**
* Combo
* @param comboId combo ID
*/
public getComboNodes(comboId: string): INode[] {
return []
}
/**
* graph animateCfg
*/
@ -1579,6 +1615,24 @@ export default class Graph extends EventEmitter implements IGraph {
}
}
// TODO 待实现 collapse 方法
/**
* combo
* @param comboId combo ID
*/
public collapse(comboId: string): void {
}
// TODO 待实现 expand 方法
/**
* combo
* @param comboId Combo ID
*/
public expand(comboId: string): void {
}
/**
*
* @param {string} groupId ID

View File

@ -1,10 +1,9 @@
import EventEmitter from '@antv/event-emitter';
import { AnimateCfg, Point } from '@antv/g-base/lib/types';
import { Point, AnimateCfg } from '@antv/g-base/lib/types';
import Graph from '../graph/graph';
import {
EdgeConfig,
GraphData,
IG6GraphEvent,
Item,
ITEM_TYPE,
ModelConfig,
@ -13,9 +12,14 @@ import {
ShapeStyle,
TreeGraphData,
LayoutConfig,
ModelStyle,
GraphOptions,
IModeOption,
IModeType,
ComboConfig,
IG6GraphEvent,
ModelStyle
} from '../types';
import { IEdge, INode } from './item';
import { IEdge, INode, ICombo } from './item';
import PluginBase from '../plugins/base';
export interface IModeOption {
@ -134,13 +138,13 @@ export interface GraphOptions {
color?: string;
} & ModelStyle;
nodeStateStyles?: {
nodeStateStyles?: {
[key: string]: ShapeStyle | {
[key: string]: ShapeStyle
}
};
edgeStateStyles?: {
edgeStateStyles?: {
[key: string]: ShapeStyle | {
[key: string]: ShapeStyle
}
@ -244,7 +248,7 @@ export interface IGraph extends EventEmitter {
*
*/
paint(): void;
/**
*
*/
@ -350,6 +354,17 @@ export interface IGraph extends EventEmitter {
*/
getEdges(): IEdge[];
/**
* combo
*/
getCombos(): ICombo[];
/**
* combo
* @param comboId Combo ID
*/
getComboNodes(comboId: string): INode[];
/**
*
* @return {number}
@ -458,6 +473,13 @@ export interface IGraph extends EventEmitter {
* @param {function} edgeFn , node
*/
edge(edgeFn: (config: EdgeConfig) => Partial<EdgeConfig>): void;
/**
* combo
* @param comboFn combo
*/
combo(comboFn: (config: ComboConfig) => Partial<ComboConfig>): void;
/**
*
* @param {number} x
@ -504,7 +526,7 @@ export interface IGraph extends EventEmitter {
*
* @param {String} name
*/
downloadFullImage(name?: string, imageConfig?: { backgroundColor?: string, padding?: number | number[]}): void;
downloadFullImage(name?: string, imageConfig?: { backgroundColor?: string, padding?: number | number[] }): void;
// TODO 需要添加布局配置类型
/**
@ -532,6 +554,18 @@ export interface IGraph extends EventEmitter {
*/
removePlugin(plugin: PluginBase): void;
/**
* Combo
* @param comboId combo ID
*/
collapse(comboId: string): void;
/**
* Combo
* @param comboId combo ID
*/
expand(comboId: string): void;
/**
*
* @param {string} groupId ID

View File

@ -283,3 +283,48 @@ export interface INode extends IItemBase {
unlock(): void;
}
export interface ICombo extends INode {
/**
* Combo ComboNode Edge
*/
getChildrens: () => ICombo[] | IEdge[];
/**
* Combo
*/
getComboNodes: () => INode[];
/**
* Combo BBox
*/
getBBox: () => IBBox;
/**
* Combo combo
* @param combo Combo ID Combo实例
* @return boolean true false
*/
addCombo: (combo: string | ICombo) => boolean;
/**
* Combo combo
* @param combo Combo ID Combo实例
* @return boolean true false
*/
removeCombo: (combo: string | ICombo) => boolean;
/**
* Combo
* @param node ID或实例
* @return boolean true false
*/
addNode: (node: string | INode) => boolean;
/**
* Combo
* @param node ID或实例
* @return boolean true false
*/
removeNode: (node: string | INode) => boolean;
}

70
src/item/combo.ts Normal file
View File

@ -0,0 +1,70 @@
import { ICombo, INode, IEdge } from '../interface/item'
import Node from './node'
import { IBBox } from '../types';
export default class Combo extends Node implements ICombo {
public getDefaultCfg() {
return {
type: 'combo',
nodes: [],
edges: []
}
}
/**
* Combo ComboNode Edge
*/
public getChildrens(): ICombo[] | IEdge[] {
return []
}
/**
* Combo
*/
getComboNodes(): INode[] {
return []
}
/**
* Combo BBox
*/
getBBox(): IBBox {
return
}
/**
* Combo combo
* @param combo Combo ID Combo实例
* @return boolean true false
*/
addCombo(combo: string | ICombo): boolean {
return true
}
/**
* Combo combo
* @param combo Combo ID Combo实例
* @return boolean true false
*/
removeCombo(combo: string | ICombo): boolean {
return true
}
/**
* Combo
* @param node ID或实例
* @return boolean true false
*/
addNode(node: string | INode): boolean {
return true
}
/**
* Combo
* @param node ID或实例
* @return boolean true false
*/
removeNode(node: string | INode): boolean {
return true
}
}

View File

@ -1,10 +1,10 @@
import GraphEvent from '@antv/g-base/lib/event/graph-event';
import { BBox } from '@antv/g-base/lib/types';
import { BBox, AnimateCfg } from '@antv/g-base/lib/types';
import Canvas from '@antv/g-canvas/lib/canvas';
import ShapeBase from '@antv/g-canvas/lib/shape/base';
import Node from '../item/node';
import { IGraph } from '../interface/graph';
import { IEdge, INode } from '../interface/item';
import { IEdge, INode, ICombo } from '../interface/item';
import { ILabelConfig } from '../interface/shape';
// Math types
@ -91,6 +91,190 @@ export type LoopConfig = Partial<{
clockwise: boolean;
}>;
export interface LayoutConfig {
type?: string;
[key: string]: unknown;
}
export interface GraphAnimateConfig extends AnimateCfg {
/**
*
*/
onFrame?: (item: Item, ratio: number, data?: GraphData, originAttrs?: ShapeStyle) => unknown;
}
export interface IModeOption {
type: string;
delegate?: boolean;
delegateStyle?: object;
updateEdge?: boolean;
trigger?: string;
enableDelegate?: boolean;
maxZoom?: number;
minZoom?: number;
enableOptimize?: boolean;
optimizeZoom?: number;
multiple?: boolean;
selectedState?: string;
includeEdges?: boolean;
direction?: 'x' | 'y';
shouldUpdate?: (e: IG6GraphEvent) => boolean;
shouldBegin?: (e: IG6GraphEvent) => boolean;
shouldEnd?: (e: IG6GraphEvent) => boolean;
onChange?: (item?: Item, judge?: boolean) => unknown;
onSelect?: (selectedNodes?: Item[], selectedEdges?: Item[]) => unknown;
onDeselect?: (selectedNodes?: Item[], selectedEdges?: Item[]) => unknown;
formatText?: (data: { [key: string]: unknown }) => string;
}
export type IModeType = string | IModeOption;
export interface IMode {
default?: IModeType[];
[key: string]: IModeType[] | undefined;
}
// Graph 配置项中 state 的类型
export interface IStates {
[key: string]: INode[];
}
export interface GraphOptions {
/**
* DOM DOM id HTML
*/
container: string | HTMLElement;
/**
* 'px'
*/
width: number;
/**
* 'px'
*/
height: number;
/**
* renderer canvas or svg
*/
renderer?: string,
fitView?: boolean;
layout?: LayoutConfig;
/**
*
* , fitViewPadding: 20
* fitViewPadding: [20, 40, 50,20]
*
*/
fitViewPadding?: Padding;
/**
* false
* true
*/
groupByTypes?: boolean;
// 是否有向图
directed?: boolean;
groupStyle?: {
style?: {
[key: string]: ShapeStyle;
};
};
/**
* setAutoPaint()
* true
*/
autoPaint?: boolean;
/**
* G6中的Mode文档
*/
modes?: IMode;
/**
* type, size, color data
*/
defaultNode?: {
shape?: string;
type?: string;
size?: number | number[];
color?: string;
} & ModelStyle;
/**
* type, size, color data
*/
defaultEdge?: {
shape?: string;
type?: string;
size?: number | number[];
color?: string;
} & ModelStyle;
/**
* Combo
*/
defaultCombo?: Partial<{
type: string;
size: number | number[];
color: string;
}> & ModelStyle;
nodeStateStyles?: {
[key: string]: ShapeStyle | {
[key: string]: ShapeStyle
}
};
edgeStateStyles?: {
[key: string]: ShapeStyle | {
[key: string]: ShapeStyle
}
};
// Combo 状态样式
comboStateStyles?: {
[key: string]: ShapeStyle | {
[key: string]: ShapeStyle
}
}
/**
* graph plugin
*/
plugins?: any[];
/**
*
*/
animate?: boolean;
/**
* animate为true时有效
*/
animateCfg?: GraphAnimateConfig;
/**
*
* 0.2
*/
minZoom?: number;
/**
*
* 10
*/
maxZoom?: number;
groupType?: string;
/**
* Edge
*/
linkCenter?: boolean;
}
// model types (node edge group)
export type ModelStyle = Partial<{
[key: string]: unknown;
@ -108,7 +292,7 @@ export type ModelStyle = Partial<{
};
// loop edge config
loopCfg: LoopConfig;
labelCfg?: ILabelConfig;
labelCfg: ILabelConfig;
}>;
export type LabelStyle = Partial<{
@ -246,6 +430,21 @@ export interface NodeConfig extends ModelConfig {
description?: string;
}
export interface ComboConfig {
id: string;
parentId?: string;
// Combo 类型,默认 rect值为定义的 combo 的名称
type: string;
// Combo 标题
title: string | LabelStyle;
style: ShapeStyle;
stateStyles: {
[key: string]: ShapeStyle | {
[key: string]: ShapeStyle
}
};
}
export interface EdgeConfig extends ModelConfig {
id?: string;
source?: string;
@ -293,6 +492,7 @@ export interface GraphData {
nodes?: NodeConfig[];
edges?: EdgeConfig[];
groups?: GroupConfig[];
combos?: ComboConfig[];
}
export interface TreeGraphData {
@ -398,10 +598,10 @@ export interface IG6GraphEvent extends GraphEvent {
target: Item & Canvas;
}
// Node Edge 实例或ID
export type Item = INode | IEdge;
// Node Edge Combo 实例
export type Item = INode | IEdge | ICombo;
export type ITEM_TYPE = 'node' | 'edge' | 'group';
export type ITEM_TYPE = 'node' | 'edge' | 'combo' | 'group';
export type NodeIdxMap = {
[key: string]: number;
@ -416,8 +616,3 @@ export interface ViewPortEventParam {
export interface Indexable<T> {
[key: string]: T;
}
export interface LayoutConfig {
type?: string;
[key: string]: unknown;
}

View File

@ -1,6 +1,6 @@
import { ModeController } from '../../../../src/graph/controller';
import Graph from '../../../../src/graph/graph';
import { GraphOptions, IGraph, IModeOption } from '../../../../src/interface/graph';
import { GraphOptions, IModeOption } from '../../../../src/types'
const div = document.createElement('div');
div.id = 'graph-spec';

View File

@ -91,7 +91,7 @@ describe('graph', () => {
).toBe(true);
const children = inst.get('group').get('children');
expect(children.length).toBe(4);
expect(children.length).toBe(5);
expect(children[1].get('className')).toEqual('edge-container');
expect(children[0].get('className')).toEqual('custom-group-container');

View File

@ -62,7 +62,7 @@ describe('graph', () => {
it('invalid container', () => {
expect(() => {
// eslint-disable-next-line no-new
new Graph({} as any);
new Graph({} as any);
}).toThrowError('invalid container');
});
@ -94,7 +94,7 @@ describe('graph', () => {
).toBe(true);
const children = inst.get('group').get('children');
expect(children.length).toBe(4);
expect(children.length).toBe(5);
expect(children[1].get('className')).toEqual('edge-container');
expect(children[0].get('className')).toEqual('custom-group-container');
@ -1319,7 +1319,7 @@ describe('behaviors', () => {
graph.emit('node:click', { item: item2 });
expect(itemKeyShape.attr('fill')).toBe('#f00');
expect(item2KeyShape.attr('fill')).toBe('#f00');
graph.emit('canvas:click');
expect(itemKeyShape.attr('fill')).toBe('#C6E5FF');
expect(item2KeyShape.attr('fill')).toBe('#C6E5FF');
@ -1357,16 +1357,16 @@ describe('behaviors', () => {
expect(evt.selectedItems.nodes.length).toBe(0);
});
graph.emit('canvas:click', { })
graph.emit('canvas:click', {})
expect(itemKeyShape.attr('fill')).toBe('#C6E5FF');
expect(item2KeyShape.attr('fill')).toBe('#C6E5FF');
});
it('drag-node', () => {
graph.emit('node:dragstart', { item, target: item, x: 0, y: 0});
graph.emit('node:drag', { item, target: item, x: 50, y: 150});
graph.emit('node:drag', { item, target: item, x: 50, y: 250});
graph.emit('node:dragend', { item, target: item, x: 50, y: 250});
graph.emit('node:dragstart', { item, target: item, x: 0, y: 0 });
graph.emit('node:drag', { item, target: item, x: 50, y: 150 });
graph.emit('node:drag', { item, target: item, x: 50, y: 250 });
graph.emit('node:dragend', { item, target: item, x: 50, y: 250 });
expect(item.getModel().x).toBe(100);
expect(item.getModel().y).toBe(300);
const edge = graph.getEdges()[0];
@ -1377,9 +1377,9 @@ describe('behaviors', () => {
const item2 = graph.getNodes()[1];
graph.setItemState(item, 'selected', true);
graph.setItemState(item2, 'selected', true);
graph.emit('node:dragstart', { item, target: item, x: 0, y: 0});
graph.emit('node:drag', { item, target: item, x: 50, y: 50});
graph.emit('node:dragend', { item, target: item, x: 50, y: 50});
graph.emit('node:dragstart', { item, target: item, x: 0, y: 0 });
graph.emit('node:drag', { item, target: item, x: 50, y: 50 });
graph.emit('node:dragend', { item, target: item, x: 50, y: 50 });
expect(item.getModel().x).toBe(150);
expect(item.getModel().y).toBe(350);
expect(item2.getModel().x).toBe(130);
@ -1629,7 +1629,7 @@ describe('layouts', () => {
});
graph.data(data);
graph.render();
graph.updateLayout({
type: 'force'
});
@ -1653,11 +1653,11 @@ describe('layouts', () => {
node.label = node.id;
});
const subdata = {
nodes: [ data.nodes[0], data.nodes[1], data.nodes[2] ],
edges: [ data.edges[0], data.edges[1] ]
nodes: [data.nodes[0], data.nodes[1], data.nodes[2]],
edges: [data.edges[0], data.edges[1]]
};
const gridLayout = new Layout['circular']({
center: [ 250, 250 ]
center: [250, 250]
});
gridLayout.init(subdata);
gridLayout.execute();
@ -1762,7 +1762,7 @@ describe('built-in items', () => {
],
};
data.nodes.forEach((node: any, i) => {
node.label = `node-${i+1}`
node.label = `node-${i + 1}`
});
const graph = new Graph({
@ -1864,13 +1864,13 @@ describe('built-in items', () => {
const polyline = graph.getEdges()[7];
graph.updateItem(polyline.getSource(), {
anchorPoints: [[ 0, 1 ]]
anchorPoints: [[0, 1]]
});
graph.updateItem(polyline.getTarget(), {
anchorPoints: [[ 1, 0.5 ]]
anchorPoints: [[1, 0.5]]
});
graph.updateItem(polyline, {
controlPoints: [ { x: 315, y: 300 } ],
controlPoints: [{ x: 315, y: 300 }],
sourceAnchor: 0,
targetAnchor: 0,
style: {
@ -2062,7 +2062,7 @@ describe('plugins', () => {
setTimeout(() => {
const minimapGroup = minimap2.get('canvas').get('children')[0];
expect(minimapGroup.get('children').length).toBe(10);
const viewport = minimap2.get('viewport');
expect(viewport.style.width).toBe('99.3377px');
expect(viewport.style.height).toBe('99.3377px');
@ -2097,7 +2097,7 @@ describe('plugins', () => {
setTimeout(() => {
const minimapGroup = minimap.get('canvas').get('children')[0];
expect(minimapGroup.get('children').length).toBe(10);
const viewport = minimap.get('viewport');
expect(viewport.style.width).toBe('99.6678px');
@ -2506,7 +2506,7 @@ describe('plugins', () => {
graph.data(bundlingData);
graph.render();
bundling.bundling(bundlingData);
graph.destroy();
});
@ -2542,17 +2542,17 @@ describe('plugins', () => {
conextMenuContainer.style.left = `${evt.x + 20}px`;
conextMenuContainer.style.top = `${evt.y}px`;
});
graph.on('node:mouseleave', () => {
conextMenuContainer.style.left = '-150px';
});
const item = graph.getNodes()[1];
graph.emit('node:contextmenu', {
graph.emit('node:contextmenu', {
x: item.getModel().x,
y: item.getModel().y
});
graph.destroy();
});
it('grid', () => {
@ -2569,7 +2569,7 @@ describe('plugins', () => {
});
graph.data(data2);
graph.render();
const gridDom = document.getElementsByClassName('g6-grid')[0] as HTMLElement;
expect(gridDom).not.toBe(undefined);
const minZoom = graph.get('minZoom');
@ -2636,10 +2636,10 @@ describe('custom group', () => {
height: 500,
renderer: 'svg',
modes: {
default: [ {
default: [{
type: 'collapse-expand-group',
trigger: 'click'
}, 'drag-node-with-group', 'drag-group' ],
}, 'drag-node-with-group', 'drag-group'],
},
});
@ -2737,7 +2737,7 @@ describe('custom group', () => {
expect(delegateGroup.get('children').length).toBe(1);
expect(node3.getModel().x).toBe(node3OriX);
expect(node3.getModel().y).toBe(node3OriY);
graph.emit('node:dragend', {
target: node3,
item: node3,