Skip to content

Commit 8dc73af

Browse files
authored
Merge pull request #263 from GREENRAT-K405/dev
2 parents 222a775 + d7d569f commit 8dc73af

4 files changed

Lines changed: 120 additions & 24 deletions

File tree

src/config/cytoscape-style.js

Lines changed: 95 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const style = [
4646
style: {
4747
curveStyle: 'bezier',
4848
targetArrowShape: 'triangle',
49+
arrowScale: 1.2,
4950
},
5051
},
5152
{
@@ -54,26 +55,113 @@ const style = [
5455
width: 'data(style.thickness)',
5556
lineColor: 'data(style.backgroundColor)',
5657
targetArrowColor: 'data(style.backgroundColor)',
57-
curveStyle: 'segments',
58-
segmentDistances: 'data(bendData.bendDistance)',
58+
curveStyle: (ele) => {
59+
const source = ele.source();
60+
const target = ele.target();
61+
62+
// Check if there are parallel edges
63+
const parallelEdges = source.edgesWith(target);
64+
const hasParallelEdges = parallelEdges.length > 1;
65+
66+
// Get positions
67+
const p1 = source.position();
68+
const p2 = target.position();
69+
70+
// Calculate distance between nodes
71+
const distance = Math.sqrt(
72+
(p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2,
73+
);
74+
75+
// Calculate difference
76+
const dx = Math.abs(p1.x - p2.x);
77+
const dy = Math.abs(p1.y - p2.y);
78+
79+
// Define a threshold for what counts as "aligned"
80+
const threshold = 10;
81+
82+
// Check if edge has custom bend data
83+
const bendDistance = ele.data('bendData')?.bendDistance || 0;
84+
const hasBend = Math.abs(bendDistance) > 0;
85+
86+
// When nodes are very close, always use straight style to prevent edge disappearance
87+
if (distance < 50) {
88+
return 'straight';
89+
}
90+
91+
// For parallel edges or edges with bend, use bezier curves
92+
if (hasParallelEdges || hasBend) {
93+
return 'unbundled-bezier';
94+
}
95+
96+
// If aligned horizontally OR vertically, be straight
97+
if (dx < threshold || dy < threshold) {
98+
return 'straight';
99+
}
100+
101+
// use unbundled-bezier to respect bend points
102+
return 'unbundled-bezier';
103+
},
104+
segmentDistances: (ele) => {
105+
// When nodes are very close, don't apply bend to prevent edge disappearance
106+
const source = ele.source();
107+
const target = ele.target();
108+
const p1 = source.position();
109+
const p2 = target.position();
110+
const distance = Math.sqrt(
111+
(p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2,
112+
);
113+
114+
if (distance < 50) {
115+
return 0;
116+
}
117+
118+
return ele.data('bendData.bendDistance');
119+
},
59120
segmentWeights: 'data(bendData.bendWeight)',
60121
edgeDistances: 'node-position',
61122
lineStyle: 'data(style.shape)',
123+
controlPointDistances: (ele) => {
124+
// For parallel edges, ensure adequate control point spacing
125+
const bendDistance = ele.data('bendData')?.bendDistance || 0;
126+
return Math.abs(bendDistance) > 0 ? bendDistance : undefined;
127+
},
128+
controlPointWeights: (ele) => {
129+
const bendWeight = ele.data('bendData')?.bendWeight;
130+
return bendWeight !== undefined ? bendWeight : 0.5;
131+
},
62132
},
63133
},
64134
{
65135
selector: 'edge[label]',
66136
style: {
67-
label: 'data(label)',
137+
label: (ele) => {
138+
// Get source and target nodes
139+
const source = ele.source();
140+
const target = ele.target();
141+
142+
// Calculate distance between nodes
143+
const p1 = source.position();
144+
const p2 = target.position();
145+
const distance = Math.sqrt(
146+
(p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2,
147+
);
148+
149+
// Define minimum distance threshold (in pixels)
150+
// Below this distance, hide the label to prevent visual clutter
151+
const minDistanceForLabel = 80;
152+
153+
// Return label only if nodes are far enough apart
154+
return distance >= minDistanceForLabel ? ele.data('label') : '';
155+
},
68156
edgeTextRotation: 'autorotate',
69157
zIndex: 999,
158+
fontSize: 12,
70159
textBackgroundOpacity: 1,
71-
color: '#000',
160+
textBackgroundPadding: '3px',
161+
textBorderWidth: 0,
162+
color: '#333',
72163
textBackgroundColor: '#fff',
73164
textBackgroundShape: 'roundrectangle',
74-
textBorderColor: '#fff',
75-
textBorderWidth: 2,
76-
textBorderOpacity: 1,
77165
},
78166
},
79167
{

src/config/defaultStyles.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ const NodeStyle = {
99
};
1010

1111
const EdgeStyle = {
12-
thickness: 1,
13-
backgroundColor: null,
12+
thickness: 2,
13+
backgroundColor: '#555',
1414
shape: 'solid',
1515
};
1616

src/graph-builder/graph-core/1-core.js

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ class CoreGraph {
3939
}
4040
// if (cy) this.cy = cy;
4141
this.cy = cytoscape({ ...cyOptions, container: element });
42+
this.cy.on('position', 'node', () => {
43+
this.cy.edges().updateStyle();
44+
});
4245
this.id = id;
4346
this.projectName = projectName;
4447
this.authorName = authorName;
@@ -81,58 +84,56 @@ class CoreGraph {
8184
this.cy.nodeEditing({
8285
resizeToContentCueEnabled: () => false,
8386
setWidth: (node, width) => {
84-
// HARD ENFORCEMENT: Snap width every frame during resize
85-
const snappedWidth = this.snapDimensionToGrid(width);
86-
node.data('style', { ...node.data('style'), width: snappedWidth });
87+
// Allow free resizing during drag - snapping will happen on resizeend
88+
node.data('style', { ...node.data('style'), width });
8789

8890
// Adjust position to maintain edge alignment based on resize handle
8991
const resizeType = node.scratch('resizeType');
9092
if (resizeType && (resizeType.includes('left') || resizeType.includes('right'))) {
9193
const currentPos = node.position();
9294
const initialPos = node.scratch('resizeInitialPos');
9395
const initialWidth = node.scratch('width');
94-
const widthDelta = snappedWidth - initialWidth;
96+
const widthDelta = width - initialWidth;
9597

9698
let newX = currentPos.x;
9799
if (resizeType.includes('left')) {
98100
newX = initialPos.x - widthDelta / 2;
99101
} else if (resizeType.includes('right')) {
100102
newX = initialPos.x + widthDelta / 2;
101103
}
102-
node.position({ x: this.snapToGrid(newX), y: currentPos.y });
104+
node.position({ x: newX, y: currentPos.y });
103105
}
104-
return snappedWidth;
106+
return width;
105107
},
106108
setHeight: (node, height) => {
107-
// HARD ENFORCEMENT: Snap height every frame during resize
108-
const snappedHeight = this.snapDimensionToGrid(height);
109-
node.data('style', { ...node.data('style'), height: snappedHeight });
109+
// Allow free resizing during drag - snapping will happen on resizeend
110+
node.data('style', { ...node.data('style'), height });
110111

111112
// Adjust position to maintain edge alignment based on resize handle
112113
const resizeType = node.scratch('resizeType');
113114
if (resizeType && (resizeType.includes('top') || resizeType.includes('bottom'))) {
114115
const currentPos = node.position();
115116
const initialPos = node.scratch('resizeInitialPos');
116117
const initialHeight = node.scratch('height');
117-
const heightDelta = snappedHeight - initialHeight;
118+
const heightDelta = height - initialHeight;
118119

119120
let newY = currentPos.y;
120121
if (resizeType.includes('top')) {
121122
newY = initialPos.y - heightDelta / 2;
122123
} else if (resizeType.includes('bottom')) {
123124
newY = initialPos.y + heightDelta / 2;
124125
}
125-
node.position({ x: currentPos.x, y: this.snapToGrid(newY) });
126+
node.position({ x: currentPos.x, y: newY });
126127
}
127-
return snappedHeight;
128+
return height;
128129
},
129130
isNoResizeMode(node) { return node.data('type') !== 'ordin'; },
130131
isNoControlsMode(node) { return node.data('type') !== 'ordin'; },
131132
});
132133

133134
this.cy.gridGuide({
134135
snapToGridOnRelease: true,
135-
snapToGridDuringDrag: true,
136+
snapToGridDuringDrag: false,
136137
zoomDash: true,
137138
panGrid: true,
138139
gridSpacing: this.gridSize,

src/graph-builder/graph-core/3-component.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class GraphComponent extends GraphCanvas {
1616

1717
constructor(...args) {
1818
super(...args);
19-
const [,,,,, nodeValidator, edgeValidator] = args;
19+
const [, , , , , nodeValidator, edgeValidator] = args;
2020
this.setEdgeNodeValidator({ nodeValidator, edgeValidator });
2121
this.getTid = () => new Date().getTime();
2222
}
@@ -70,7 +70,14 @@ class GraphComponent extends GraphCanvas {
7070
if (targetID === edge.target().id()) dists.add(edge.data('bendData').bendDistance);
7171
else dists.add(-edge.data('bendData').bendDistance);
7272
});
73-
for (let d = 0; ;d += 20) {
73+
74+
// Calculate optimal spacing based on number of parallel edges
75+
// Base spacing of 60px, increasing with more edges for better visibility
76+
const edgeCount = edges.length;
77+
const baseSpacing = 100;
78+
const spacingIncrement = edgeCount > 4 ? baseSpacing * 1.5 : baseSpacing;
79+
80+
for (let d = 0; ; d += spacingIncrement) {
7481
if (!dists.has(d)) return d;
7582
if (!dists.has(-d)) return -d;
7683
}

0 commit comments

Comments
 (0)