fix: polyline with negative endpoints; fix: polyline direction when linkCenter; fix: remove g6-core browser since it has no umd output; feat: custom texts for the time range and time point text in timeBar plugin; chore: types for strict mode;

This commit is contained in:
Yanyan-Wang 2021-01-31 13:39:43 +08:00
parent 5cdb068efb
commit 83b9c75b73
23 changed files with 194 additions and 78 deletions

View File

@ -1,5 +1,17 @@
# ChangeLog
#### 4.1.7
- fix: polyline with negative endpoints;
- fix: polyline direction when linkCenter;
- fix: remove g6-core browser since it has no umd output;
- feat: custom texts for the time range and time point text in timeBar plugin;
- chore: types for strict mode;
#### 4.1.6
- fix: webworker problem after removing broswer in pc and g6;
#### 4.1.5
- fix: wrong style for modelRect after updating and state changing, closes: #2613;

View File

@ -1,6 +1,6 @@
{
"name": "@antv/g6-core",
"version": "0.0.8",
"version": "0.0.9",
"description": "A Graph Visualization Framework in JavaScript",
"keywords": [
"antv",
@ -31,7 +31,6 @@
],
"main": "lib/index.js",
"module": "es/index.js",
"browser": "dist/g6-core.min.js",
"types": "lib/index.d.ts",
"scripts": {
"start": "father build --watch",

View File

@ -64,7 +64,7 @@ const colorSet = {
};
export default {
version: '0.0.8',
version: '0.0.9',
rootContainerClassName: 'root-container',
nodeContainerClassName: 'node-container',
edgeContainerClassName: 'edge-container',

View File

@ -360,7 +360,7 @@ export interface IAbstractGraph extends EventEmitter {
* @param {string} mode default
* @return {Graph} Graph
*/
updateBehavior: (behavior: string, newCfg: object, modes: string | string[]) => Graph;
updateBehavior: (behavior: string, newCfg: object, mode?: string) => Graph;
/**
*

View File

@ -363,5 +363,5 @@ export interface ICombo extends INode {
* @param node
* @return boolean true false
*/
removeNode: (node: string | INode) => boolean;
removeNode: (node: INode) => boolean;
}

View File

@ -1,7 +1,7 @@
import { IGroup } from '@antv/g-base';
import { ICombo, INode, IItemBaseConfig } from '../interface/item';
import Node from './node';
import { ComboConfig, IBBox, IShapeBase } from '../types';
import { ComboConfig, IBBox, IShapeBase, ModelConfig } from '../types';
import Global from '../global';
import { getBBox } from '../util/graphic';
import { isNumber } from '@antv/util';
@ -21,7 +21,7 @@ export default class Combo extends Node implements ICombo {
};
}
public getShapeCfg(model: ComboConfig): ComboConfig {
public getShapeCfg(model: ModelConfig): ModelConfig {
const styles = this.get('styles');
const bbox = this.get('bbox');
if (styles && bbox) {

View File

@ -1,6 +1,6 @@
import { each, isNil, mix } from '@antv/util';
import { IEdge, INode } from '../interface/item';
import { IPoint, IShapeBase, NodeConfig } from '../types';
import { IPoint, IShapeBase, ModelConfig, NodeConfig } from '../types';
import { getBBox } from '../util/graphic';
import {
distance,
@ -237,7 +237,7 @@ export default class Node extends Item implements INode {
*
* @param cfg
*/
public isOnlyMove(cfg: NodeConfig): boolean {
public isOnlyMove(cfg: ModelConfig): boolean {
if (!cfg) {
return false;
}

View File

@ -47,7 +47,7 @@ const isBetween = (value: number, min: number, max: number) => value >= min && v
* @return {Point}
*/
export const getLineIntersect = (p0: Point, p1: Point, p2: Point, p3: Point): Point | null => {
const tolerance = 0.001;
const tolerance = 0.0001;
const E: Point = {
x: p2.x - p0.x,
@ -63,12 +63,13 @@ export const getLineIntersect = (p0: Point, p1: Point, p2: Point, p3: Point): Po
};
const kross: number = D0.x * D1.y - D0.y * D1.x;
const sqrKross: number = kross * kross;
const invertKross: number = 1 / kross;
const sqrLen0: number = D0.x * D0.x + D0.y * D0.y;
const sqrLen1: number = D1.x * D1.x + D1.y * D1.y;
let point: Point | null = null;
if (sqrKross > tolerance * sqrLen0 * sqrLen1) {
const s = (E.x * D1.y - E.y * D1.x) / kross;
const t = (E.x * D0.y - E.y * D0.x) / kross;
const s = (E.x * D1.y - E.y * D1.x) * invertKross;
const t = (E.x * D0.y - E.y * D0.x) * invertKross;
if (isBetween(s, 0, 1) && isBetween(t, 0, 1)) {
point = {
x: p0.x + s * D0.x,

View File

@ -1,6 +1,6 @@
{
"name": "@antv/g6-element",
"version": "0.0.7",
"version": "0.0.8",
"description": "A Graph Visualization Framework in JavaScript",
"keywords": [
"antv",
@ -61,7 +61,7 @@
},
"dependencies": {
"@antv/g-base": "^0.5.1",
"@antv/g6-core": "^0.0.8",
"@antv/g6-core": "^0.0.9",
"@antv/util": "~2.0.5"
},
"devDependencies": {

View File

@ -66,14 +66,13 @@ export const filterConnectPoints = (points: PolyPoint[]): PolyPoint[] => {
// pre-process: remove duplicated points
const result: any[] = [];
const pointsMap: any = {};
points.forEach((p) => {
const id = `${p.x}-${p.y}`;
p.id = id;
pointsMap[id] = p;
});
each(pointsMap, (p) => {
const pointsLength = points.length;
for (let i = 0; i < pointsLength; i++) {
const p = points[i];
p.id = `${p.x}|||${p.y}`;
pointsMap[p.id] = p;
result.push(p);
});
}
return result;
};
export const simplifyPolyline = (points: PolyPoint[]): PolyPoint[] => {
@ -102,13 +101,37 @@ export const getExpandedBBox = (bbox: any, offset: number): PBBox => {
width: bbox.width + 2 * offset,
};
};
export const isHorizontalPort = (port: PolyPoint, bbox: PBBox): boolean => {
export const isHorizontalPort = (port: PolyPoint, bbox: PBBox): boolean | number => {
const dx = Math.abs(port.x - bbox.centerX);
const dy = Math.abs(port.y - bbox.centerY);
if (dx === 0 && dy === 0) return 0;
return dx / bbox.width > dy / bbox.height;
};
export const getExpandedBBoxPoint = (bbox: any, point: PolyPoint): PolyPoint => {
export const getExpandedBBoxPoint = (
bbox: any,
point: PolyPoint,
anotherPoint: PolyPoint,
): PolyPoint => {
const isHorizontal = isHorizontalPort(point, bbox);
if (isHorizontal === 0) {
// 说明锚点是节点中心linkCenter: true。需要根据两个节点的相对关系决定方向
let x = bbox.centerX;
let y = bbox.centerY;
if (anotherPoint.y < point.y) {
// 另一端在左上/右上方时,总是从上方走
y = bbox.minY;
} else if (anotherPoint.x > point.x) {
// 另一端在右下方,往右边走
x = bbox.maxX;
} else if (anotherPoint.x < point.x) {
// 另一端在左下方,往左边走
x = bbox.minX;
} else if (anotherPoint.x === point.x) {
// 另一段在正下方,往下走
y = bbox.maxY;
}
return { x, y };
}
if (isHorizontal) {
return {
x: point.x > bbox.centerX ? bbox.maxX : bbox.minX,
@ -266,15 +289,21 @@ export const isSegmentsIntersected = (
p2: PolyPoint,
p3: PolyPoint,
): boolean => {
const s1X = p1.x - p0.x;
const s1Y = p1.y - p0.y;
const s2X = p3.x - p2.x;
const s2Y = p3.y - p2.y;
const v1x = p2.x - p0.x;
const v1y = p2.y - p0.y;
const v2x = p3.x - p0.x;
const v2y = p3.y - p0.y;
const v3x = p2.x - p1.x;
const v3y = p2.y - p1.y;
const v4x = p3.x - p1.x;
const v4y = p3.y - p1.y;
const s = (-s1Y * (p0.x - p2.x) + s1X * (p0.y - p2.y)) / (-s2X * s1Y + s1X * s2Y);
const t = (s2X * (p0.y - p2.y) - s2Y * (p0.x - p2.x)) / (-s2X * s1Y + s1X * s2Y);
const pd1 = v1x * v2y - v1y * v2x;
const pd2 = v3x * v4y - v3y * v4x;
const pd3 = v1x * v3y - v1y * v3x;
const pd4 = v2x * v4y - v2y * v4x;
return s >= 0 && s <= 1 && t >= 0 && t <= 1;
return pd1 * pd2 <= 0 && pd3 * pd4 <= 0;
};
export const isSegmentCrossingBBox = (p1: PolyPoint, p2: PolyPoint, bbox: PBBox): boolean => {
if (bbox.width === 0 && bbox.height === 0) {
@ -483,10 +512,9 @@ export const getPolylinePoints = (
// the expanded bounding boxes of source and target nodes are overlapping
return simplifyPolyline(getSimplePolyline(start, end));
}
const sPoint = getExpandedBBoxPoint(sxBBox, start);
const tPoint = getExpandedBBoxPoint(txBBox, end);
const sPoint = getExpandedBBoxPoint(sxBBox, start, end);
const tPoint = getExpandedBBoxPoint(txBBox, end, start);
const lineBBox = getBBoxFromPoints([sPoint, tPoint]);
const outerBBox = mergeBBox(sxBBox, txBBox);
const sMixBBox = mergeBBox(sxBBox, lineBBox);
const tMixBBox = mergeBBox(txBBox, lineBBox);
let connectPoints: any = [];

View File

@ -37,7 +37,7 @@ registerEdge(
routeCfg: {
obstacles: [], // 希望边绕过的障碍节点
maxAllowedDirectionChange: 90, // 允许的最大转角
maximumLoops: 1000,
maximumLoops: 500,
gridSize: 10, // 指定精度
},
stateStyles: {

View File

@ -97,8 +97,9 @@ export const octolinearCfg: RouterCfg = {
};
const pos2GridIx = (pos: number, gridSize: number) => {
const gridIx = Math.floor(pos / gridSize);
return gridIx < 0 ? 0 : gridIx;
const gridIx = Math.floor(Math.abs(pos / gridSize));
const sign = pos < 0 ? -1 : 1;
return gridIx < 0 ? 0 : sign * gridIx;
};
const getObstacleMap = (items: Item[], gridSize: number, offset: number) => {
@ -113,7 +114,7 @@ const getObstacleMap = (items: Item[], gridSize: number, offset: number) => {
y <= pos2GridIx(bbox.maxY, gridSize);
y += 1
) {
const gridKey = `${x}-${y}`;
const gridKey = `${x}|||${y}`;
map[gridKey] = true;
}
}
@ -158,7 +159,13 @@ const estimateCost = (from: PolyPoint, endPoints: PolyPoint[], distFunc) => {
};
// 计算考虑 offset 后的 BBox 上的连接点
const getBoxPoints = (point: PolyPoint, node: INode, cfg: RouterCfg): PolyPoint[] => {
const getBoxPoints = (
point: PolyPoint,
oriPoint: PolyPoint,
node: INode,
anotherPoint: PolyPoint,
cfg: RouterCfg,
): PolyPoint[] => {
const points = [];
// create-edge 生成边的过程中endNode 为 null
if (!node) {
@ -167,15 +174,16 @@ const getBoxPoints = (point: PolyPoint, node: INode, cfg: RouterCfg): PolyPoint[
const { directions, offset } = cfg;
const bbox = node.getBBox();
const expandBBox = getExpandedBBox(node.getBBox(), offset);
const isInside =
oriPoint.x > bbox.minX &&
oriPoint.x < bbox.maxX &&
oriPoint.y > bbox.minY &&
oriPoint.y < bbox.maxY;
const expandBBox = getExpandedBBox(bbox, offset);
for (const i in expandBBox) {
expandBBox[i] = pos2GridIx(expandBBox[i], cfg.gridSize);
}
const isInside =
point.x > pos2GridIx(bbox.minX, cfg.gridSize) &&
point.x < pos2GridIx(bbox.maxX, cfg.gridSize) &&
point.y > pos2GridIx(bbox.minY, cfg.gridSize) &&
point.y < pos2GridIx(bbox.maxY, cfg.gridSize);
if (isInside) {
// 如果 anchorPoint 在节点内部,允许第一段线穿过节点
@ -230,16 +238,16 @@ const getBoxPoints = (point: PolyPoint, node: INode, cfg: RouterCfg): PolyPoint[
boundLine[0],
boundLine[1],
) as PolyPoint;
if (insterctP && !isSegmentCrossingBBox(point, insterctP, node.getBBox())) {
insterctP.id = `${insterctP.x}-${insterctP.y}`;
if (insterctP && !isSegmentCrossingBBox(point, insterctP, bbox)) {
insterctP.id = `${insterctP.x}|||${insterctP.y}`;
points.push(insterctP);
}
}
}
} else {
// 如果 anchorPoint 在节点上,只有一个可选方向
const insterctP = getExpandedBBoxPoint(expandBBox, point);
insterctP.id = `${insterctP.x}-${insterctP.y}`;
const insterctP = getExpandedBBoxPoint(expandBBox, point, anotherPoint);
insterctP.id = `${insterctP.x}|||${insterctP.y}`;
points.push(insterctP);
}
@ -266,10 +274,10 @@ export const pathFinder = (
y: pos2GridIx(endPoint.y, cfg.gridSize),
};
startPoint.id = `${scaleStartPoint.x}-${scaleStartPoint.y}`;
endPoint.id = `${scaleEndPoint.x}-${scaleEndPoint.y}`;
const startPoints = getBoxPoints(scaleStartPoint, startNode, cfg);
const endPoints = getBoxPoints(scaleEndPoint, endNode, cfg);
startPoint.id = `${scaleStartPoint.x}|||${scaleStartPoint.y}`;
endPoint.id = `${scaleEndPoint.x}|||${scaleEndPoint.y}`;
const startPoints = getBoxPoints(scaleStartPoint, startPoint, startNode, scaleEndPoint, cfg);
const endPoints = getBoxPoints(scaleEndPoint, endPoint, endNode, scaleStartPoint, cfg);
startPoints.forEach((point) => {
delete map[point.id];
});
@ -311,8 +319,8 @@ export const pathFinder = (
} else {
const prevDirectionAngle = getDirectionAngle(
{
x: parseFloat(cameFrom[current.id].split('-')[0]),
y: parseFloat(cameFrom[current.id].split('-')[1]),
x: parseFloat(cameFrom[current.id].split('|||')[0]),
y: parseFloat(cameFrom[current.id].split('|||')[1]),
},
current,
);
@ -324,8 +332,8 @@ export const pathFinder = (
const getControlPoints = (currentId: string) => {
const controlPoints = [endPoint];
const lastPoint = {
x: parseFloat(currentId.split('-')[0]),
y: parseFloat(currentId.split('-')[1]),
x: parseFloat(currentId.split('|||')[0]),
y: parseFloat(currentId.split('|||')[1]),
id: currentId,
};
if (getDirectionChange(lastPoint, scaleEndPoint)) {
@ -336,14 +344,14 @@ export const pathFinder = (
}
while (cameFrom[currentId] && cameFrom[currentId] !== currentId) {
const point = {
x: parseFloat(currentId.split('-')[0]),
y: parseFloat(currentId.split('-')[1]),
x: parseFloat(currentId.split('|||')[0]),
y: parseFloat(currentId.split('|||')[1]),
id: currentId,
};
const preId = cameFrom[currentId];
const prePoint = {
x: parseFloat(preId.split('-')[0]),
y: parseFloat(preId.split('-')[1]),
x: parseFloat(preId.split('|||')[0]),
y: parseFloat(preId.split('|||')[1]),
id: preId,
};
const directionChange = getDirectionChange(prePoint, point);
@ -359,8 +367,8 @@ export const pathFinder = (
// 和startNode对齐
const firstPoint = {
x: parseFloat(currentId.split('-')[0]),
y: parseFloat(currentId.split('-')[1]),
x: parseFloat(currentId.split('|||')[0]),
y: parseFloat(currentId.split('|||')[1]),
id: currentId,
};
controlPoints[0].x = firstPoint.x === scaleStartPoint.x ? startPoint.x : controlPoints[0].x;
@ -399,7 +407,7 @@ export const pathFinder = (
const neighbor = {
x: current.x + direction.stepX,
y: current.y + direction.stepY,
id: `${current.x + direction.stepX}-${current.y + direction.stepY}`,
id: `${current.x + direction.stepX}|||${current.y + direction.stepY}`,
};
if (closedSet[neighbor.id]) continue;

View File

@ -1,6 +1,6 @@
{
"name": "@antv/g6",
"version": "4.1.6",
"version": "4.1.7",
"description": "A Graph Visualization Framework in JavaScript",
"keywords": [
"antv",
@ -66,7 +66,7 @@
]
},
"dependencies": {
"@antv/g6-pc": "^0.0.11"
"@antv/g6-pc": "^0.0.12"
},
"devDependencies": {
"@babel/core": "^7.7.7",

View File

@ -1,7 +1,7 @@
import G6 from '@antv/g6-pc';
G6.version = '4.1.6';
G6.version = '4.1.7';
export * from '@antv/g6-pc';
export default G6;
export const version = '4.1.6';
export const version = '4.1.7';

View File

@ -1,6 +1,6 @@
{
"name": "@antv/g6-pc",
"version": "0.0.11",
"version": "0.0.12",
"description": "A Graph Visualization Framework in JavaScript",
"keywords": [
"antv",
@ -75,9 +75,9 @@
"@antv/g-math": "^0.1.1",
"@antv/g-svg": "^0.5.1",
"@antv/g-webgpu": "^0.5.1",
"@antv/g6-core": "^0.0.8",
"@antv/g6-plugin": "^0.0.7",
"@antv/g6-element": "^0.0.7",
"@antv/g6-core": "^0.0.9",
"@antv/g6-plugin": "^0.0.8",
"@antv/g6-element": "^0.0.8",
"@antv/algorithm": "^0.0.7",
"@antv/hierarchy": "^0.6.2",
"@antv/layout": "^0.0.16",

View File

@ -7,7 +7,7 @@ const textColor = 'rgb(0, 0, 0)';
const colorSet = getColorsWithSubjectColor(subjectColor, backColor);
export default {
version: '0.0.11',
version: '0.0.12',
rootContainerClassName: 'root-container',
nodeContainerClassName: 'node-container',
edgeContainerClassName: 'edge-container',

View File

@ -0,0 +1,61 @@
import { Graph } from '../../../src';
import '../../../src';
const div = document.createElement('div');
div.id = 'edge-shape';
document.body.appendChild(div);
describe('polyline edge', () => {
it('polyline edge', () => {
const graph = new Graph({
container: div,
width: 500,
height: 500,
// linkCenter: true,
modes: {
default: ['drag-node', 'zoom-canvas', 'drag-canvas'],
},
defaultEdge: {
type: 'polyline',
},
defaultNode: {
type: 'rect',
size: [10, 10],
},
fitCenter: true,
});
const data = {
nodes: [
{
id: '1',
x: -100,
y: -300,
},
{
id: '2',
x: -200,
y: -200,
},
],
edges: [
{
source: '1',
target: '2',
},
],
};
graph.data(data);
graph.render();
const edge = graph.getEdges()[0];
const keyShape = edge.getKeyShape();
const path = keyShape.attr('path');
// expect(path[0][1]).toBe(100);
// expect(path[0][2]).toBe(300);
// expect(path[2][1]).toBe(100);
// expect(path[2][2]).toBe(200);
// expect(path[4][1]).toBe(200);
// expect(path[4][2]).toBe(200);
// graph.destroy();
});
});

View File

@ -1,6 +1,6 @@
{
"name": "@antv/g6-plugin",
"version": "0.0.7",
"version": "0.0.8",
"description": "G6 Plugin",
"main": "lib/index.js",
"module": "es/index.js",

View File

@ -60,6 +60,7 @@ export type ControllerCfg = Partial<{
readonly playBtnStyle?: ShapeStyle;
readonly fontFamily?: string;
readonly timePointControllerText?: string;
readonly timeRangeControllerText?: string;
}>;
export default class ControllerBtn {
@ -366,11 +367,11 @@ export default class ControllerBtn {
const isChecked = evt.target.get('isChecked');
if (!isChecked) {
this.checkedIcon.show();
this.checkedText.attr('text', '时间范围');
this.checkedText.attr('text', this.controllerCfg?.timeRangeControllerText || '时间范围');
this.currentType = 'single';
} else {
this.checkedIcon.hide();
this.checkedText.attr('text', '单一时间');
this.checkedText.attr('text', this.controllerCfg?.timePointControllerText || '单一时间');
this.currentType = 'range';
}
evt.target.set('isChecked', !isChecked);

View File

@ -1,4 +1,5 @@
{
"private": true,
"name": "@antv/g6-react-node",
"description": "Using React Component to Define Your G6 Graph Node",
"version": "1.3.0",

View File

@ -626,6 +626,7 @@ type ControllerCfg = Partial<{
readonly playBtnStyle?: ShapeStyle;
/** the text for the right-botton switch controlling play with single time point or time range */
readonly timePointControllerText?: string;
readonly timeRangeControllerText?: string
}>
```
@ -646,6 +647,7 @@ type ControllerCfg = Partial<{
| nextBtnStyle | ShapeStyle | null | The style configuration for the forward button |
| playBtnStyle | ShapeStyle | null | The style configuration for the play button |
| timePointControllerText | string | "单一时间" | The text for the right-botton switch controlling play with single time point or time range |
| timeRangeControllerText | string | "时间范围" | The text for the right-botton switch controlling play with single time point or time range |
## ToolTip
ToolTip helps user to explore detail infomations on the node and edge. Do note that, This Tooltip Plugins will replace the tooltip in the built-in behavior after G6 4.0.

View File

@ -814,8 +814,9 @@ type ControllerCfg = Partial<{
readonly nextBtnStyle?: ShapeStyle;
/** 播放按钮样式 */
readonly playBtnStyle?: ShapeStyle;
/** 右下角“单一时间”文本 */
/** 右下角“单一时间”和“时间范围”文本 */
readonly timePointControllerText?: string;
readonly timeRangeControllerText?: string
}>
```
@ -836,3 +837,4 @@ type ControllerCfg = Partial<{
| nextBtnStyle | ShapeStyle | null | 前进按钮样式配置项 |
| playBtnStyle | ShapeStyle | null | 播放按钮样式配置项 |
| timePointControllerText | string | "单一时间" | 右下角“单一时间”文本,默认为”单一时间“ |
| timePointControllerText | string | "时间范围" | 右下角“单一时间”文本,默认为”时间范围时间“ |

View File

@ -35,7 +35,7 @@
"@antv/chart-node-g6": "^0.0.3",
"@antv/util": "^2.0.9",
"@antv/g6": "^4.1.6",
"@antv/gatsby-theme-antv": "^1.0.5",
"@antv/gatsby-theme-antv": "1.0.6",
"gatsby": "^2.24.40",
"gh-pages": "^2.1.1",
"typedoc": "^0.17.6",
@ -43,7 +43,8 @@
"@types/react": "^16.9.35",
"@types/react-dom": "^16.9.8",
"react-i18next": "^11.1.0",
"@ant-design/icons": "^4.0.6"
"@ant-design/icons": "^4.0.6",
"typescript": "^3.6.5"
},
"resolutions": {
"@types/react": "^16.9.35"