mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-25 12:06:00 +00:00
diagram scoring
This commit is contained in:
@@ -481,7 +481,7 @@
|
|||||||
|
|
||||||
graph.initialize();
|
graph.initialize();
|
||||||
|
|
||||||
const layout = GraphLayout.createCircle(graph).springyAlg().fixViewBox();
|
const layout = GraphLayout.createCircle(graph).springyAlg().doMoveSteps().fixViewBox();
|
||||||
|
|
||||||
callChange(current => {
|
callChange(current => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { rectangleDistance, Vector2D } from './designerMath';
|
import { IBoxBounds, rectangleDistance, rectangleIntersectArea, Vector2D } from './designerMath';
|
||||||
|
|
||||||
const MIN_NODE_DISTANCE = 50;
|
const MIN_NODE_DISTANCE = 50;
|
||||||
const SPRING_LENGTH = 100;
|
const SPRING_LENGTH = 100;
|
||||||
@@ -7,6 +7,11 @@ const SPRINGY_STEPS = 50;
|
|||||||
const GRAVITY = 0.01;
|
const GRAVITY = 0.01;
|
||||||
const REPULSION = 500_000;
|
const REPULSION = 500_000;
|
||||||
const MAX_FORCE_SIZE = 100;
|
const MAX_FORCE_SIZE = 100;
|
||||||
|
const NODE_MARGIN = 20;
|
||||||
|
const MOVE_STEP = 20;
|
||||||
|
const MOVE_BIG_STEP = 70;
|
||||||
|
const MOVE_STEP_COUNT = 1000;
|
||||||
|
const MINIMAL_SCORE_BENEFIT = 1;
|
||||||
|
|
||||||
class GraphNode {
|
class GraphNode {
|
||||||
neightboors: GraphNode[] = [];
|
neightboors: GraphNode[] = [];
|
||||||
@@ -69,6 +74,7 @@ class LayoutNode {
|
|||||||
right: number;
|
right: number;
|
||||||
top: number;
|
top: number;
|
||||||
bottom: number;
|
bottom: number;
|
||||||
|
paddedRect: IBoxBounds;
|
||||||
|
|
||||||
constructor(public node: GraphNode, public x: number, public y: number) {
|
constructor(public node: GraphNode, public x: number, public y: number) {
|
||||||
this.left = x - node.width / 2;
|
this.left = x - node.width / 2;
|
||||||
@@ -76,6 +82,13 @@ class LayoutNode {
|
|||||||
this.right = x + node.width / 2;
|
this.right = x + node.width / 2;
|
||||||
this.bottom = y + node.height / 2;
|
this.bottom = y + node.height / 2;
|
||||||
this.position = new Vector2D(x, y);
|
this.position = new Vector2D(x, y);
|
||||||
|
|
||||||
|
this.paddedRect = {
|
||||||
|
left: this.left - NODE_MARGIN,
|
||||||
|
top: this.top - NODE_MARGIN,
|
||||||
|
right: this.right + NODE_MARGIN,
|
||||||
|
bottom: this.bottom + NODE_MARGIN,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
translate(dx: number, dy: number) {
|
translate(dx: number, dy: number) {
|
||||||
@@ -83,16 +96,11 @@ class LayoutNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
distanceTo(node: LayoutNode) {
|
distanceTo(node: LayoutNode) {
|
||||||
return rectangleDistance(
|
return rectangleDistance(this, node);
|
||||||
this.left,
|
}
|
||||||
this.top,
|
|
||||||
this.right,
|
intersectArea(node: LayoutNode) {
|
||||||
this.bottom,
|
return rectangleIntersectArea(this.paddedRect, node.paddedRect);
|
||||||
node.left,
|
|
||||||
node.top,
|
|
||||||
node.right,
|
|
||||||
node.bottom
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,8 +241,8 @@ export class GraphLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fixViewBox() {
|
fixViewBox() {
|
||||||
const minX = _.min(_.values(this.nodes).map(n => n.x - n.node.width / 2));
|
const minX = _.min(_.values(this.nodes).map(n => n.left));
|
||||||
const minY = _.min(_.values(this.nodes).map(n => n.y - n.node.height / 2));
|
const minY = _.min(_.values(this.nodes).map(n => n.top));
|
||||||
|
|
||||||
return this.changePositions(n => n.translate(-minX + 50, -minY + 50));
|
return this.changePositions(n => n.translate(-minX + 50, -minY + 50));
|
||||||
}
|
}
|
||||||
@@ -254,4 +262,72 @@ export class GraphLayout {
|
|||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
score() {
|
||||||
|
let res = 0;
|
||||||
|
for (const n1 of _.values(this.nodes)) {
|
||||||
|
for (const n2 of _.values(this.nodes)) {
|
||||||
|
if (n1.node.designerId == n2.node.designerId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
res += n1.intersectArea(n2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const minX = _.min(_.values(this.nodes).map(n => n.left));
|
||||||
|
const minY = _.min(_.values(this.nodes).map(n => n.top));
|
||||||
|
const maxX = _.max(_.values(this.nodes).map(n => n.right));
|
||||||
|
const maxY = _.max(_.values(this.nodes).map(n => n.bottom));
|
||||||
|
|
||||||
|
res += maxX - minX;
|
||||||
|
res += maxY - minY;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
tryMoveNode(node: LayoutNode): GraphLayout[] {
|
||||||
|
return [
|
||||||
|
this.changePositions(x => (x == node ? node.translate(MOVE_STEP, 0) : x)),
|
||||||
|
this.changePositions(x => (x == node ? node.translate(-MOVE_STEP, 0) : x)),
|
||||||
|
this.changePositions(x => (x == node ? node.translate(0, MOVE_STEP) : x)),
|
||||||
|
this.changePositions(x => (x == node ? node.translate(0, -MOVE_STEP) : x)),
|
||||||
|
|
||||||
|
this.changePositions(x => (x == node ? node.translate(MOVE_BIG_STEP, MOVE_BIG_STEP) : x)),
|
||||||
|
this.changePositions(x => (x == node ? node.translate(MOVE_BIG_STEP, -MOVE_BIG_STEP) : x)),
|
||||||
|
this.changePositions(x => (x == node ? node.translate(-MOVE_BIG_STEP, MOVE_BIG_STEP) : x)),
|
||||||
|
this.changePositions(x => (x == node ? node.translate(-MOVE_BIG_STEP, -MOVE_BIG_STEP) : x)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
tryMoveElement() {
|
||||||
|
let res = null;
|
||||||
|
let resScore = null;
|
||||||
|
|
||||||
|
for (const node of _.values(this.nodes)) {
|
||||||
|
for (const item of this.tryMoveNode(node)) {
|
||||||
|
const score = item.score();
|
||||||
|
if (resScore == null || score < resScore) {
|
||||||
|
res = item;
|
||||||
|
resScore = score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
doMoveSteps() {
|
||||||
|
let res: GraphLayout = this;
|
||||||
|
let score = res.score();
|
||||||
|
for (let step = 0; step < MOVE_STEP_COUNT; step++) {
|
||||||
|
const lastRes = res;
|
||||||
|
res = res.tryMoveElement();
|
||||||
|
const newScore = res.score();
|
||||||
|
// console.log('SCORE, NEW SCORE', score, newScore);
|
||||||
|
if (score - newScore < MINIMAL_SCORE_BENEFIT) return lastRes;
|
||||||
|
score = newScore;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ interface IPoint {
|
|||||||
y: number;
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IBoxBounds {
|
export interface IBoxBounds {
|
||||||
left: number;
|
left: number;
|
||||||
top: number;
|
top: number;
|
||||||
right: number;
|
right: number;
|
||||||
@@ -56,26 +56,26 @@ export function intersectLineBox(p1: IPoint, p2: IPoint, box: IBoxBounds): IPoin
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rectangleDistance(
|
export function rectangleDistance(r1: IBoxBounds, r2: IBoxBounds) {
|
||||||
x1: number,
|
|
||||||
y1: number,
|
|
||||||
x1b: number,
|
|
||||||
y1b: number,
|
|
||||||
x2: number,
|
|
||||||
y2: number,
|
|
||||||
x2b: number,
|
|
||||||
y2b: number
|
|
||||||
) {
|
|
||||||
function dist(x1: number, y1: number, x2: number, y2: number) {
|
function dist(x1: number, y1: number, x2: number, y2: number) {
|
||||||
let dx = x1 - x2;
|
let dx = x1 - x2;
|
||||||
let dy = y1 - y2;
|
let dy = y1 - y2;
|
||||||
return Math.sqrt(dx * dx + dy * dy);
|
return Math.sqrt(dx * dx + dy * dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
let left = x2b < x1;
|
const x1 = r1.left;
|
||||||
let right = x1b < x2;
|
const y1 = r1.top;
|
||||||
let bottom = y2b < y1;
|
const x1b = r1.right;
|
||||||
let top = y1b < y2;
|
const y1b = r1.bottom;
|
||||||
|
const x2 = r2.left;
|
||||||
|
const y2 = r2.top;
|
||||||
|
const x2b = r2.right;
|
||||||
|
const y2b = r2.bottom;
|
||||||
|
|
||||||
|
const left = x2b < x1;
|
||||||
|
const right = x1b < x2;
|
||||||
|
const bottom = y2b < y1;
|
||||||
|
const top = y1b < y2;
|
||||||
|
|
||||||
if (top && left) return dist(x1, y1b, x2b, y2);
|
if (top && left) return dist(x1, y1b, x2b, y2);
|
||||||
else if (left && bottom) return dist(x1, y1, x2b, y2b);
|
else if (left && bottom) return dist(x1, y1, x2b, y2b);
|
||||||
@@ -89,6 +89,12 @@ export function rectangleDistance(
|
|||||||
else return 0;
|
else return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function rectangleIntersectArea(rect1: IBoxBounds, rect2: IBoxBounds) {
|
||||||
|
const x_overlap = Math.max(0, Math.min(rect1.right, rect2.right) - Math.max(rect1.left, rect2.left));
|
||||||
|
const y_overlap = Math.max(0, Math.min(rect1.bottom, rect2.bottom) - Math.max(rect1.top, rect2.top));
|
||||||
|
return x_overlap * y_overlap;
|
||||||
|
}
|
||||||
|
|
||||||
export class Vector2D {
|
export class Vector2D {
|
||||||
constructor(public x: number, public y: number) {}
|
constructor(public x: number, public y: number) {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user