feat: 将pointer重新定义为marker

This commit is contained in:
黎智洲 2021-07-21 23:51:58 +08:00
parent a0ebd04892
commit da2d43581e
13 changed files with 230 additions and 125 deletions

2
dist/sv.js vendored

File diff suppressed because one or more lines are too long

View File

@ -97,7 +97,7 @@ export const Util = {
* @returns
*/
convertG6Data(layoutGroup: LayoutGroup): G6Data {
let nodes = [...layoutGroup.element, ...layoutGroup.pointer],
let nodes = [...layoutGroup.element, ...layoutGroup.marker],
edges = layoutGroup.link;
return {

View File

@ -1,15 +1,15 @@
import { Util } from "../Common/util";
import { Engine } from "../engine";
import { ElementOption, Layouter, LayoutGroupOptions, LinkOption, PointerOption } from "../options";
import { ElementOption, Layouter, LayoutGroupOptions, LinkOption, MarkerOption } from "../options";
import { sourceLinkData, SourceElement, LinkTarget, Sources } from "../sources";
import { SV } from "../StructV";
import { Element, Link, Model, Pointer } from "./modelData";
import { Element, Link, Marker, Model } from "./modelData";
export type LayoutGroup = {
element: Element[];
link: Link[];
pointer: Pointer[];
marker: Marker[];
layouter: Layouter;
layouterName: string;
options: LayoutGroupOptions;
@ -32,7 +32,7 @@ export class ModelConstructor {
}
/**
* elementlink和pointer
* elementlink和marker
* @param sourceList
*/
public construct(sources: Sources): LayoutGroupTable {
@ -52,7 +52,7 @@ export class ModelConstructor {
let sourceDataString: string = JSON.stringify(sourceGroup.data),
prevString: string = this.prevSourcesStringMap[name],
elementList: Element[] = [],
pointerList: Pointer[] = [];
markerList: Marker[] = [];
if(prevString === sourceDataString) {
return;
@ -61,18 +61,18 @@ export class ModelConstructor {
const options: LayoutGroupOptions = optionsTable[layouterName],
sourceData = layouter.sourcesPreprocess(sourceGroup.data, options),
elementOptions = options.element || { },
pointerOptions = options.pointer || { };
markerOptions = options.marker || { };
elementList = this.constructElements(elementOptions, name, sourceData, layouterName);
pointerList = this.constructPointers(pointerOptions, elementList);
markerList = this.constructMarkers(markerOptions, elementList);
layoutGroupTable.set(name, {
element: elementList,
link: [],
pointer: pointerList,
marker: markerList,
options: options,
layouter: layouter,
modelList: [...elementList, ...pointerList],
modelList: [...elementList, ...markerList],
layouterName,
isHide: false
});
@ -179,31 +179,31 @@ export class ModelConstructor {
}
/**
* element pointer
* @param pointerOptions
* element marker
* @param markerOptions
* @param elements
* @returns
*/
private constructPointers(pointerOptions: { [key: string]: PointerOption }, elements: Element[]): Pointer[] {
let pointerList: Pointer[] = [],
pointerNames = Object.keys(pointerOptions);
private constructMarkers(markerOptions: { [key: string]: MarkerOption }, elements: Element[]): Marker[] {
let markerList: Marker[] = [],
markerNames = Object.keys(markerOptions);
pointerNames.forEach(name => {
markerNames.forEach(name => {
for(let i = 0; i < elements.length; i++) {
let element = elements[i],
pointerData = element[name];
markerData = element[name];
// 若没有指针字段的结点则跳过
if(!pointerData) continue;
if(!markerData) continue;
let id = name + '.' + (Array.isArray(pointerData)? pointerData.join('-'): pointerData),
pointer = this.createPointer(id, name, pointerData, element, pointerOptions[name]);
let id = name + '.' + (Array.isArray(markerData)? markerData.join('-'): markerData),
marker = this.createMarker(id, name, markerData, element, markerOptions[name]);
pointerList.push(pointer);
markerList.push(marker);
}
});
return pointerList;
return markerList;
}
/**
@ -249,20 +249,20 @@ export class ModelConstructor {
}
/**
* Pointer
* marker
* @param id
* @param pointerName
* @param markerName
* @param label
* @param target
* @param options
*/
private createPointer(id: string, pointerName: string, pointerData: string | string[], target: Element, options: PointerOption): Pointer {
let pointer = undefined;
private createMarker(id: string, markerName: string, markerData: string | string[], target: Element, options: MarkerOption): Marker {
let marker = undefined;
pointer = new Pointer(id, pointerName, pointerData, target);
pointer.initProps(options);
marker = new Marker(id, markerName, markerData, target);
marker.initProps(options);
return pointer;
return marker;
};
/**

View File

@ -1,5 +1,5 @@
import { Util } from "../Common/util";
import { ElementLabelOption, ElementOption, LinkLabelOption, LinkOption, PointerOption, Style } from "../options";
import { ElementLabelOption, ElementOption, LinkLabelOption, LinkOption, MarkerOption, Style } from "../options";
import { SourceElement } from "../sources";
import { BoundingRect } from "../Common/boundingRect";
import { SV } from './../StructV';
@ -16,7 +16,7 @@ export interface G6NodeModel {
label: string | string[];
style: Style;
labelCfg: ElementLabelOption;
externalPointerId: string;
markerId: string;
SVLayouter: string;
SVModelType: string;
SVModelName: string;
@ -64,7 +64,7 @@ export class Model {
* G6 model
* @param option
*/
protected defineProps(option: ElementOption | LinkOption | PointerOption) {
protected defineProps(option: ElementOption | LinkOption | MarkerOption) {
return null;
}
@ -72,7 +72,7 @@ export class Model {
* G6 model
* @param option
*/
initProps(option: ElementOption | LinkOption | PointerOption) {
initProps(option: ElementOption | LinkOption | MarkerOption) {
this.props = this.defineProps(option);
}
@ -169,7 +169,7 @@ export class Element extends Model {
groupName: string;
layouterName: string;
freed: boolean;
pointers: { [key: string]: Pointer };
markers: { [key: string]: Marker };
constructor(id: string, type: string, group: string, layouter: string, sourceElement: SourceElement) {
super(id, type);
@ -190,7 +190,7 @@ export class Element extends Model {
this.sourceId = this.id.split('.')[1];
this.sourceElement = sourceElement;
this.pointers = { };
this.markers = { };
}
protected defineProps(option: ElementOption): G6NodeModel {
@ -206,7 +206,7 @@ export class Element extends Model {
label: option.label,
style: Util.objectClone<Style>(option.style),
labelCfg: Util.objectClone<ElementLabelOption>(option.labelOptions),
externalPointerId: null,
markerId: null,
SVLayouter: this.layouterName,
SVModelType: 'element',
SVModelName: this.type
@ -260,7 +260,7 @@ export class Link extends Model {
export class Pointer extends Model {
export class Marker extends Model {
target: Element;
label: string | string[];
anchor: number;
@ -270,28 +270,38 @@ export class Pointer extends Model {
this.target = target;
this.label = label;
this.target.set('externalPointerId', id);
this.target.pointers[type] = this;
this.target.set('markerId', id);
this.target.markers[type] = this;
}
protected defineProps(option: PointerOption): G6NodeModel {
getLabelSizeRadius(): number {
const { width, height } = this.G6Item.getContainer().getChildren()[2].getBBox();
return width > height? width: height;
}
protected defineProps(option: MarkerOption): G6NodeModel {
this.anchor = option.anchor;
const type = option.type,
defaultSize: [number, number] = type === 'pointer'? [8, 30]: [12, 12];
return {
id: this.id,
x: 0,
y: 0,
rotation: 0,
type: option.type || 'external-pointer',
size: option.size || [8, 50],
type: option.type || 'marker',
size: option.size || defaultSize,
anchorPoints: null,
label: typeof this.label === 'string'? this.label: this.label.join(', '),
style: Util.objectClone<Style>(option.style),
labelCfg: Util.objectClone<ElementLabelOption>(option.labelOptions),
externalPointerId: null,
markerId: null,
SVLayouter: null,
SVModelType: 'pointer',
SVModelType: 'marker',
SVModelName: this.type
};
}
};

View File

@ -0,0 +1,86 @@
import * as G6 from "./../Lib/g6.js";
export default G6.registerNode('cursor', {
draw(cfg, group) {
const keyShape = group.addShape('path', {
attrs: {
path: this.getPath(cfg),
fill: cfg.style.fill,
matrix: cfg.style.matrix
},
name: 'cursor-path'
});
if (cfg.label) {
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
const bgRect = group.addShape('rect', {
attrs: {
x: 0,
y: 0,
text: cfg.label,
fill: '#fafafa',
radius: 2,
},
name: 'bgRect'
});
const text = group.addShape('text', {
attrs: {
x: 0,
y: 0,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: style.fill || '#999',
fontSize: style.fontSize || 16
},
name: 'cursor-text-shape'
});
const { width: textWidth, height: textHeight } = text.getBBox();
bgRect.attr({
width: textWidth + 6,
height: textHeight + 6
});
// 旋转文字
const markerEndPosition = cfg.markerEndPosition;
if(markerEndPosition) {
let textX = markerEndPosition[0],
textY = markerEndPosition[1];
text.attr({
x: textX,
y: textY
});
bgRect.attr({
x: textX - textWidth / 2 - 3,
y: textY - textHeight / 2 - 3
});
}
}
return keyShape;
},
getPath(cfg) {
let width = cfg.size[0],
height = cfg.size[1];
const path = [
['M', 0, 0],
['L', -width / 2, -height],
['L', width / 2, -height],
['L', 0, 0],
['Z'],
];
return path;
}
});

View File

@ -1,7 +1,7 @@
import * as G6 from "./../Lib/g6.js";
import * as G6 from "../Lib/g6.js";
export default G6.registerNode('external-pointer', {
export default G6.registerNode('pointer', {
draw(cfg, group) {
const keyShape = group.addShape('path', {
attrs: {
@ -30,7 +30,7 @@ export default G6.registerNode('external-pointer', {
attrs: {
x: 0,
y: 0,
textAlign: 'left',
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: style.fill || '#999',
@ -46,12 +46,10 @@ export default G6.registerNode('external-pointer', {
});
// 旋转文字
const pointerEndPosition = cfg.pointerEndPosition;
if(pointerEndPosition) {
let textX = pointerEndPosition[0] - textWidth / 2,
textY = pointerEndPosition[1],
rectWidth = bgRect.attr('width'),
rectHeight = bgRect.attr('height');
const markerEndPosition = cfg.markerEndPosition;
if(markerEndPosition) {
let textX = markerEndPosition[0],
textY = markerEndPosition[1];
text.attr({
x: textX,
@ -59,8 +57,8 @@ export default G6.registerNode('external-pointer', {
});
bgRect.attr({
x: pointerEndPosition[0] - rectWidth / 2,
y: pointerEndPosition[1] - rectHeight / 2
x: textX - textWidth / 2 - 3,
y: textY - textHeight / 2 - 3
});
}
}

View File

@ -1,15 +1,15 @@
import { Engine } from "./engine";
import { Bound } from "./Common/boundingRect";
import { Group } from "./Common/group";
import externalPointer from "./RegisteredShape/externalPointer";
import pointer from "./RegisteredShape/pointer";
import * as G6 from "./Lib/g6.js";
import linkListNode from "./RegisteredShape/linkListNode";
import binaryTreeNode from "./RegisteredShape/binaryTreeNode";
import twoCellNode from "./RegisteredShape/twoCellNode";
import Cursor from "./RegisteredShape/cursor";
import { Vector } from "./Common/vector";
import indexedNode from "./RegisteredShape/indexedNode";
import { EngineOptions, Layouter } from "./options";
import { LayoutGroup } from "./Model/modelConstructor";
import { SourceElement } from "./sources";
import { Element } from "./Model/modelData";
@ -49,11 +49,13 @@ SV.G6 = G6;
SV.registeredLayouter = {};
SV.registeredShape = [
externalPointer,
pointer,
linkListNode,
binaryTreeNode,
twoCellNode,
indexedNode
indexedNode,
Cursor
];
SV.registerShape = G6.registerNode;

View File

@ -1,5 +1,5 @@
import { Engine } from "../../engine";
import { Model, Pointer } from "../../Model/modelData";
import { Model, Marker } from "../../Model/modelData";
import { AnimationOptions, InteractionOptions, LayoutGroupOptions } from "../../options";
import { SV } from "../../StructV";
import { Animations } from "../animation";
@ -95,15 +95,40 @@ export class Container {
* @param list
* @returns
*/
protected findReTargetPointer(list: Model[]): Pointer[] {
let prevPointers = this.prevModelList.filter(item => item instanceof Pointer),
pointers = list.filter(item => item instanceof Pointer);
protected findReTargetMarkers(list: Model[]): Marker[] {
let prevMarkers = this.prevModelList.filter(item => item instanceof Marker),
markers = list.filter(item => item instanceof Marker);
return <Pointer[]>pointers.filter(item => prevPointers.find(prevItem => {
return prevItem.id === item.id && (<Pointer>prevItem).target.id !== (<Pointer>item).target.id
return <Marker[]>markers.filter(item => prevMarkers.find(prevItem => {
return prevItem.id === item.id && (<Marker>prevItem).target.id !== (<Marker>item).target.id
}));
}
/**
* label model
* @param list
*/
protected findLabelChangeModels(list: Model[]): Model[] {
let labelChangeModels: Model[] = [];
list.forEach(item => {
const prevItem = this.prevModelList.find(prevItem => prevItem.id === item.id);
if(prevItem === undefined) {
return;
}
const prevLabel = prevItem.get('label'),
label = item.get('label');
if(prevLabel !== label) {
labelChangeModels.push(item);
}
});
return labelChangeModels;
}
/**
* G6Item
* @param appendData
@ -184,7 +209,11 @@ export class Container {
public render(modelList: Model[]) {
const appendModels: Model[] = this.getAppendModels(this.prevModelList, modelList),
removeModels: Model[] = this.getRemoveModels(this.prevModelList, modelList),
changeModels: Model[] = [...appendModels, ...this.findReTargetPointer(modelList)];
changeModels: Model[] = [
...appendModels,
...this.findLabelChangeModels(modelList),
...this.findReTargetMarkers(modelList)
];
// 渲染视图
this.renderer.render(modelList, removeModels);

View File

@ -1,7 +1,6 @@
import { Engine } from "../../engine";
import { Link, Model } from "../../Model/modelData";
import { InteractionOptions, LayoutGroupOptions } from "../../options";
import { Animations } from "../animation";
import { Container } from "./container";
@ -10,32 +9,9 @@ import { Container } from "./container";
*
*/
export class MainContainer extends Container {
private leakContainerPosition: { x: number, y: number } = null;
private freedContainerPosition: { x: number, y: number } = null;
constructor(engine: Engine, DOMContainer: HTMLElement, g6Options: { [key: string]: any } = { }) {
super(engine, DOMContainer, g6Options);
this.leakContainerPosition = this.getDOMPosition(this.engine.engineOptions.leakContainer);
this.freedContainerPosition = this.getDOMPosition(this.engine.engineOptions.freedContainer);
}
/**
*
* @param dom
* @returns
*/
private getDOMPosition(dom: HTMLElement): { x: number, y: number } {
if(dom === null) {
return { x: 0, y: 0 };
}
const bound = dom.getBoundingClientRect();
return {
x: bound.x + bound.width / 2,
y: bound.y + bound.height / 2
};
}
/**

View File

@ -3,8 +3,8 @@ import { Group } from '../Common/group';
import { Vector } from '../Common/vector';
import { Engine } from '../engine';
import { LayoutGroupTable } from '../Model/modelConstructor';
import { Element, Model, Pointer } from '../Model/modelData';
import { LayoutOptions, PointerOption, ViewOptions } from '../options';
import { Element, Model, Marker } from '../Model/modelData';
import { LayoutOptions, MarkerOption, ViewOptions } from '../options';
import { Container } from './container/container';
@ -21,10 +21,10 @@ export class Layouter {
/**
*
* @param elements
* @param pointers
* @param markers
*/
private initLayoutValue(elements: Element[], pointers: Pointer[]) {
[...elements, ...pointers].forEach(item => {
private initLayoutValue(elements: Element[], markers: Marker[]) {
[...elements, ...markers].forEach(item => {
item.set('rotation', item.get('rotation'));
item.set({ x: 0, y: 0 });
});
@ -32,21 +32,22 @@ export class Layouter {
/**
*
* @param pointer
* @param pointerOptions
* @param marker
* @param markerOptions
*/
private layoutPointer(pointers: Pointer[], pointerOptions: { [key: string]: PointerOption }) {
pointers.forEach(item => {
const options: PointerOption = pointerOptions[item.getType()],
offset = options.offset || 8,
anchor = item.anchor || 0;
private layoutMarker(markers: Marker[], markerOptions: { [key: string]: MarkerOption }) {
markers.forEach(item => {
const options: MarkerOption = markerOptions[item.getType()],
offset = options.offset ?? 8,
anchor = item.anchor ?? 0,
labelOffset = options.labelOffset ?? 2;
let target = item.target,
targetBound: BoundingRect = target.getBound(),
anchorPosition = item.target.G6Item.getAnchorPoints()[anchor],
center: [number, number] = [targetBound.x + targetBound.width / 2, targetBound.y + targetBound.height / 2],
pointerPosition: [number, number],
pointerEndPosition: [number, number];
markerPosition: [number, number],
markerEndPosition: [number, number];
anchorPosition = [anchorPosition.x, anchorPosition.y];
@ -60,18 +61,19 @@ export class Layouter {
angle = Math.sign(anchorVector[0]) * (Math.PI / 2 - Math.atan(anchorVector[1] / anchorVector[0]));
}
const pointerHeight = item.get('size')[1];
const markerHeight = item.get('size')[1],
labelRadius = item.getLabelSizeRadius() / 2;
anchorVector = Vector.normalize(anchorVector);
pointerPosition = Vector.location(center, anchorVector, len);
pointerEndPosition = Vector.location(center, anchorVector, pointerHeight + len);
pointerEndPosition = Vector.subtract(pointerEndPosition, pointerPosition);
markerPosition = Vector.location(center, anchorVector, len);
markerEndPosition = Vector.location(center, anchorVector, markerHeight + len + labelRadius + labelOffset);
markerEndPosition = Vector.subtract(markerEndPosition, markerPosition);
item.set({
x: pointerPosition[0],
y: pointerPosition[1],
x: markerPosition[0],
y: markerPosition[1],
rotation: angle,
pointerEndPosition
markerEndPosition
});
});
}
@ -110,13 +112,13 @@ export class Layouter {
modelGroup.add(item);
});
this.initLayoutValue(group.element, group.pointer); // 初始化布局参数
this.initLayoutValue(group.element, group.marker); // 初始化布局参数
group.layouter.layout(group.element, options); // 布局节点
modelGroupList.push(modelGroup);
});
layoutGroupTable.forEach(group => {
this.layoutPointer(group.pointer, group.options.pointer); // 布局外部指针
this.layoutMarker(group.marker, group.options.marker); // 布局外部指针
});
return modelGroupList;

View File

@ -81,7 +81,7 @@ export class ViewManager {
freedList.forEach(fItem => {
removeModels.push(...freedGroup.element.splice(freedGroup.element.findIndex(item => item.id === fItem.id), 1));
removeModels.push(...freedGroup.link.splice(freedGroup.link.findIndex(item => item.element.id === fItem.id || item.target.id === fItem.id)));
removeModels.push(...freedGroup.pointer.splice(freedGroup.pointer.findIndex(item => item.target.id === fItem.id)));
removeModels.push(...freedGroup.marker.splice(freedGroup.marker.findIndex(item => item.target.id === fItem.id)));
});
removeModels.map(model => {

View File

@ -1,4 +1,4 @@
import { Element, Link, Pointer } from "./Model/modelData";
import { Element, Link, Marker } from "./Model/modelData";
import { Sources } from "./sources";
import { LayoutGroupTable, ModelConstructor } from "./Model/modelConstructor";
import { AnimationOptions, EngineOptions, InteractionOptions, LayoutGroupOptions, ViewOptions } from "./options";
@ -139,22 +139,22 @@ export class Engine {
}
/**
* pointer
* marker
* @param group
*/
public getPointers(group?: string): Pointer[] {
public getMarkers(group?: string): Marker[] {
const layoutGroupTable = this.modelConstructor.getLayoutGroupTable();
if(group && layoutGroupTable.has('group')) {
return layoutGroupTable.get('group').pointer;
return layoutGroupTable.get('group').marker;
}
const pointers: Pointer[] = [];
const markers: Marker[] = [];
layoutGroupTable.forEach(item => {
pointers.push(...item.pointer);
markers.push(...item.marker);
})
return pointers;
return markers;
}
/**

View File

@ -54,12 +54,14 @@ export interface LinkOption {
}
export interface PointerOption extends ElementOption {
export interface MarkerOption extends ElementOption {
anchor: number;
offset: number;
labelOffset: number;
};
export interface LayoutOptions {
[key: string]: any;
};
@ -74,7 +76,7 @@ export interface BehaviorOptions {
export interface LayoutGroupOptions {
element: { [key: string]: ElementOption };
link?: { [key: string]: LinkOption }
pointer?: { [key: string]: PointerOption };
marker?: { [key: string]: MarkerOption }
layout?: LayoutOptions;
behavior?: BehaviorOptions;
};