Using mxParallelEdgeLayout for edges between vertices of different parents - javascript

I use mxParallelEdgeLayout to allow for multiple parallel edges between vertices.
This works fine as long as the vertices share the same parent. However, once I connect vertices which are children of different parents, the automatically chosen connection points by the edges look weird:
Find the full, runnable sample code here:
<html>
<head>
<!-- Sets the basepath for the library -->
<script type="text/javascript">
mxBasePath = 'https://jgraph.github.io/mxgraph/javascript/src';
</script>
<!-- loads mxGraph library -->
<script type="text/javascript" src="https://jgraph.github.io/mxgraph/javascript/src/js/mxClient.js"></script>
<script type="text/javascript">
function main(container) {
if (!mxClient.isBrowserSupported()) {
mxUtils.error('Browser is not supported!', 200, false);
} else {
var graph = new mxGraph(container);
// vertex style
var style = graph.getStylesheet().getDefaultVertexStyle();
delete style[mxConstants.STYLE_FILLCOLOR];
style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
// edge style
var edge_style = graph.getStylesheet().getDefaultEdgeStyle();
edge_style[mxConstants.STYLE_ENDARROW] = 0;
var parent = graph.getDefaultParent();
// layout supporting parallel edges
var layout = new mxParallelEdgeLayout(graph);
layout.spacing = 10;
var layoutMgr = new mxLayoutManager(graph);
layoutMgr.getLayout = function(cell) {
return layout;
};
graph.getModel().beginUpdate();
try {
var box1 = graph.insertVertex(parent, null, null, 0, 0, 100, 50);
box1.setConnectable(false);
var box2 = graph.insertVertex(parent, null, null, 0, 80, 100, 50);
box2.setConnectable(false);
var v1 = graph.insertVertex(box1, null, null, 10, 10, 30, 30);
v1.setConnectable(true);
var v2 = graph.insertVertex(box1, null, null, 60, 10, 30, 30);
v2.setConnectable(true);
var v3 = graph.insertVertex(box2, null, null, 15, 10, 30, 30);
v3.setConnectable(true);
// connect vertices within same parents
graph.insertEdge(parent, null, null, v1, v2)
graph.insertEdge(parent, null, null, v1, v2)
graph.insertEdge(parent, null, null, box1, box2)
graph.insertEdge(parent, null, null, box1, box2)
// connect vertices embedded in different parents
graph.insertEdge(parent, null, null, v1, v3)
graph.insertEdge(parent, null, null, v1, v3)
} finally {
graph.getModel().endUpdate();
}
}
};
</script>
</head>
<body onload="main(document.getElementById('graphContainer'))">
<div id="graphContainer" />
</body>
</html>
I suspect the unexpected behaviour is somewhat related to the implementation of mxLayoutManager ... but I have trouble to fix it. Any help is very much appreciated.

Related

Transparency of multiple tile layers without colors leaking (phaser.js)

So I have been using the Chebyshev Distance example from the Phaser labs, and while this example was using one layer, I happen to be using two, and when i set transparency on them, the colors start leaking into each other, especially on light colors.
Is there any way to circumvent or get rid of this effect
If the problem is that you have two layers, one ontop of the other and you are making both transparent (or only the top one), and you don't want that color to pass through, the solution could be to hide the tiles on the bottom layer.
Just check in the map-tile-loop, if the tile, where you want to change the alpha, has a tile beneath it, and if so make that background tile transparent.
Here a small working demo:
(The main magic is in the updateMap function)
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536,
height: 183,
scene: {
preload,
create
}
};
var player;
var bgLayer;
var point1 = {x: 250, y: 31};
var isLeaking = false;
new Phaser.Game(config);
function preload (){
this.load.image('tiles', 'https://labs.phaser.io/assets/tilemaps/tiles/catastrophi_tiles_16.png');
this.load.tilemapCSV('map', 'https://labs.phaser.io/assets/tilemaps/csv/catastrophi_level2.csv');
}
function create () {
this.add.text(50, 1, ' <- Background is visible, if no tiles are ontop')
.setOrigin(0)
.setDepth(100)
.setStyle({fontFamily: 'Arial'});
this.infoText = this.add.text(10, 20, 'Click to toggle leaking: on')
.setOrigin(0)
.setDepth(100)
.setStyle({fontFamily: 'Arial'});
// Just creating image for second layer tiles //
let graphics = this.make.graphics();
graphics.fillStyle(0xff0000);
graphics.fillRect(0, 0, 16, 16);
graphics.generateTexture('tiles2', 16, 16);
// Just creating image for second layer tiles //
let map = this.make.tilemap({ key: 'map', tileWidth: 16, tileHeight: 16 });
let tileset = map.addTilesetImage('tiles');
let tileset2 = map.addTilesetImage('tiles2');
bgLayer = map.createBlankLayer('background', tileset2);
bgLayer.fill(0);
let fgLayer = map.createLayer(0, tileset, 0, 0);
// Just to show that the Background is still show if not Tile is covering
fgLayer.removeTileAt(0, 0);
fgLayer.removeTileAt(1, 0);
fgLayer.removeTileAt(2, 0);
player = this.add.rectangle(point1.x, point1.y, 5, 5, 0xffffff, .5)
.setOrigin(.5);
this.input.on('pointerdown', () => {
isLeaking = !isLeaking;
this.infoText.setText( `Click to toggle leaking: ${isLeaking?'off':'on'}` )
updateMap(map);
});
updateMap(map);
}
function updateMap (map) {
let originPoint1 = map.getTileAtWorldXY(point1.x, point1.y);
console.info(map.layers.sort((a,b) => b.depth - a.depth))
map.forEachTile(function (tile) {
var dist = Phaser.Math.Distance.Chebyshev(
originPoint1.x,
originPoint1.y,
tile.x,
tile.y
);
let bgTile = bgLayer.getTileAt(tile.x, tile.y, false)
let hideOnlyTheseTiles = [ 0, 1, 2, 3, 4]; // Indexes to hide
if( !isLeaking ){
if(hideOnlyTheseTiles.indexOf(bgTile.index) > -1){ // here yopu can select the
bgTile.setAlpha(0);
}
} else{
bgTile.setAlpha(1);
}
tile.setAlpha(1 - 0.09 * dist);
});
}
<script src="//cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>

Change collision width and height of individual tiles in a tilemap

I'm working on a game in Phaser 3, and need to be able to change the collision width and height of the wall tiles to something other than the width of the images, but I can't find anything that doesn't involve Tiled, which I can't use as it's a procedurally generated game.
I found a method to change the size of a tile, and I know how to get and individual tile, but nothing to change the collision size, and the few leads I found involved the differenced between the deprecated createDynamicLayer and createStaticLayer methods. The physics property of the tile object is empty, and doesn't contain the physics body of the tile, even though I set up collision between the wall tiles and the player (arcade physics). Any suggestions? thanks!
If you don't want to use the greate application "Tiled", the easiest option would be to set the tiles that should have a partial collision to not collide, and than iterate over the Map tiles and place invisible static physics bodies ontop.
It is maybe not very elegant, but it works well, and if you don't have > 1000 partial tiles on Screen, this should not be a performance issue.
Here is a Demo:
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536 /8,
height: 42,
zoom: 4.5,
physics: {
default: 'arcade',
arcade: {
gravity:{ y: 0 },
debug: false
}
},
scene: {
create,
update
},
banner: false
};
function create () {
let graphics = this.make.graphics();
graphics.fillStyle(0x00ff00);
graphics.fillRect(8, 0, 8, 8);
graphics.fillStyle(0xff0000);
graphics.fillRect(16, 0, 8, 8);
graphics.fillStyle(0xff0000);
graphics.fillRect(24, 0, 8, 8);
graphics.generateTexture('tiles', 8*4, 8);
var level = [
[1,1,1,1,1],
[1,0,0,0,1],
[1,0,3,0,1],
[1,0,0,0,1],
[1,1,1,1,1],
]
// Map for the first level
var map = this.make.tilemap({ data: level, tileWidth: 8, tileHeight: 8 });
var tiles = map.addTilesetImage('tiles');
var layer = map.createLayer(0, tiles, 0, 0);
layer.setCollision(1);
this.player = this.add.circle(16,16,4, 0xffffff)
.setDepth(2);
this.physics.add.existing(this.player);
this.physics.add.collider(layer, this.player);
let partialCollisions = [];
map.forEachTile((tile) => {
if(tile.index == 3){
let partialCollition = this.add.circle(tile.pixelX + 4, tile.pixelY + 4, 1)
this.physics.add.existing(partialCollition, true);
partialCollisions.push(partialCollition)
}
});
this.physics.add.collider(partialCollisions, this.player);
this.keys = this.input.keyboard.createCursorKeys();
}
function update(){
let speed = 20;
this.player.body.setVelocity(0)
if(this.keys.up.isDown){
this.player.body.setVelocityY(-speed)
}
if(this.keys.down.isDown){
this.player.body.setVelocityY(speed)
}
if(this.keys.left.isDown){
this.player.body.setVelocityX(-speed)
}
if(this.keys.right.isDown){
this.player.body.setVelocityX(speed)
}
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>

Connect edges to anchor points of vertices in mxgraph

I'm using mxGraph (javascript version) to draw a graph and I need to use radial tree layout for graph layout. Look at this pen
I managed to redefine connection points (anchor points) of each vertex and you can see that by hovering the mouse over any vertex. I assumed that the radial tree layout will connect edges to anchor points of vertices, but this is not happening. Any idea how to make edges connect to anchor points of vertices?
The same code in the Pen is also available below.
<html>
<head>
<title>Anchors example for mxGraph</title>
<script type="text/javascript">
mxBasePath = 'https://jgraph.github.io/mxgraph/javascript/src';
</script>
<!-- Loads and initializes the library -->
<script type="text/javascript" src="https://jgraph.github.io/mxgraph/javascript/src/js/mxClient.js">
</script>
<!-- Example code -->
<script type="text/javascript">
// Overridden to define per-shape connection points
mxGraph.prototype.getAllConnectionConstraints = function(terminal, source)
{
if (terminal != null && terminal.shape != null)
{
if (terminal.shape.stencil != null)
{
if (terminal.shape.stencil.constraints != null)
{
return terminal.shape.stencil.constraints;
}
}
else if (terminal.shape.constraints != null)
{
return terminal.shape.constraints;
}
}
return null;
};
// Defines the default constraints for all shapes
mxShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0), true),
new mxConnectionConstraint(new mxPoint(0.5, 0), true),
new mxConnectionConstraint(new mxPoint(0.75, 0), true),
new mxConnectionConstraint(new mxPoint(0, 0.25), true),
new mxConnectionConstraint(new mxPoint(0, 0.5), true),
new mxConnectionConstraint(new mxPoint(0, 0.75), true),
new mxConnectionConstraint(new mxPoint(1, 0.25), true),
new mxConnectionConstraint(new mxPoint(1, 0.5), true),
new mxConnectionConstraint(new mxPoint(1, 0.75), true),
new mxConnectionConstraint(new mxPoint(0.25, 1), true),
new mxConnectionConstraint(new mxPoint(0.5, 1), true),
new mxConnectionConstraint(new mxPoint(0.75, 1), true)];
// Edges have no connection points
mxPolyline.prototype.constraints = null;
// Program starts here. Creates a sample graph in the
// DOM node with the specified ID. This function is invoked
// from the onLoad event handler of the document (see below).
function main(container)
{
// Checks if the browser is supported
if (!mxClient.isBrowserSupported())
{
// Displays an error message if the browser is not supported.
mxUtils.error('Browser is not supported!', 200, false);
}
else
{
// Disables the built-in context menu
mxEvent.disableContextMenu(container);
// Creates the graph inside the given container
var graph = new mxGraph(container);
graph.setConnectable(true);
// Enables connect preview for the default edge style
graph.connectionHandler.createEdgeState = function(me)
{
var edge = graph.createEdge(null, null, null, null, null);
return new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge));
};
// Specifies the default edge style
//graph.getStylesheet().getDefaultEdgeStyle()['edgeStyle'] = 'orthogonalEdgeStyle';
graph.getStylesheet().getDefaultEdgeStyle()[mxConstants.STYLE_EDGE] = mxEdgeStyle.scalePointArray;
// Enables rubberband selection
new mxRubberband(graph);
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
var parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.getModel().beginUpdate();
try
{
var v1 = graph.insertVertex(parent, null, 'Hello,', 0, 0, 80, 30);
var v2 = graph.insertVertex(parent, null, 'World!', 0, 0, 80, 30);
var v3 = graph.insertVertex(parent, null, 'Hello,', 0, 0, 80, 30);
var v4 = graph.insertVertex(parent, null, 'World!', 0, 0, 80, 30);
var v5 = graph.insertVertex(parent, null, 'World!', 0, 0, 80, 30);
var v6 = graph.insertVertex(parent, null, 'World!', 0, 0, 80, 30);
var v7 = graph.insertVertex(parent, null, 'World!', 0, 0, 80, 30);
var e1 = graph.insertEdge(parent, null, '', v1, v2);
var e2 = graph.insertEdge(parent, null, '', v1, v3);
var e3 = graph.insertEdge(parent, null, '', v1, v4);
var e4 = graph.insertEdge(parent, null, '', v1, v5);
var e5 = graph.insertEdge(parent, null, '', v1, v6);
var e6 = graph.insertEdge(parent, null, '', v1, v7);
}
finally
{
var radialLayout = new mxRadialTreeLayout(graph);
radialLayout.execute(parent);
// Updates the display
graph.getModel().endUpdate();
}
console.log(v1.geometry);
console.log(v2.geometry);
}
};
</script>
</head>
<!-- Page passes the container for the graph to the program -->
<body onload="main(document.getElementById('graphContainer'))">
<!-- Creates a container for the graph with a grid wallpaper -->
<div id="graphContainer"
style="position:relative;overflow:hidden;width:621px;height:641px;background:url('editors/images/grid.gif');cursor:default;">
</div>
</body>
</html>
You can do that via setting constraints in edge style while adding it:
var e1 = graph.insertEdge(parent, null, '',v1,v2, "entryX=0.25;entryY=0");
that way you can also specify starting point of edge:
var e1 = graph.insertEdge(parent, null, '',v1,v2, "entryX=0.25;entryY=0;exitX=1;exitY=1");
Or after inserting edge you can connect it with specific constraint:
var e2 = graph.insertEdge(parent, null, '', v1, v3);
graph.connectCell(e2,v3,false, mxShape.prototype.constraints[0]);

Constraints mxGraph

I'm creating my own drawer online with mxGraph. I want to make my ConnectionConstraints always visibles on the sheet after the creation of my cell. I tried to edit mxConstraintHandler but it didn't worked. This is what I want, even after the mouse left the shape --> enter image description here. The 'x' on the shape have to be always persistent.
Instead you can try ports. that's Using the isPort hook for visually connecting to another cell. And for 'x' shape use offset of geometry.
Please check Hello Port example
// Program starts here. Creates a sample graph in the
// DOM node with the specified ID. This function is invoked
// from the onLoad event handler of the document (see below).
function main(container)
{
// Checks if the browser is supported
if (!mxClient.isBrowserSupported())
{
// Displays an error message if the browser is not supported.
mxUtils.error('Browser is not supported!', 200, false);
}
else
{
// Creates the graph inside the given container
var graph = new mxGraph(container);
graph.setConnectable(true);
graph.setTooltips(true);
// Sets the default edge style
var style = graph.getStylesheet().getDefaultEdgeStyle();
style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;
// Ports are not used as terminals for edges, they are
// only used to compute the graphical connection point
graph.isPort = function(cell)
{
var geo = this.getCellGeometry(cell);
return (geo != null) ? geo.relative : false;
};
// Implements a tooltip that shows the actual
// source and target of an edge
graph.getTooltipForCell = function(cell)
{
if (this.model.isEdge(cell))
{
return this.convertValueToString(this.model.getTerminal(cell, true)) + ' => ' +
this.convertValueToString(this.model.getTerminal(cell, false))
}
return mxGraph.prototype.getTooltipForCell.apply(this, arguments);
};
// Removes the folding icon and disables any folding
graph.isCellFoldable = function(cell)
{
return false;
};
// Enables rubberband selection
new mxRubberband(graph);
// Gets the default parent for inserting new cells. This
// is normally the first child of the root (ie. layer 0).
var parent = graph.getDefaultParent();
// Adds cells to the model in a single step
graph.getModel().beginUpdate();
try
{
var v1 = graph.insertVertex(parent, null, 'Hello', 20, 80, 80, 30);
v1.setConnectable(false);
var v11 = graph.insertVertex(v1, null, '', 1, 1, 10, 10);
v11.geometry.offset = new mxPoint(-5, -5);
v11.geometry.relative = true;
var v12 = graph.insertVertex(v1, null, '', 1, 0, 10, 10);
v12.geometry.offset = new mxPoint(-5, -5);
v12.geometry.relative = true;
var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
var v3 = graph.insertVertex(parent, null, 'World2', 200, 20, 80, 30);
var e1 = graph.insertEdge(parent, null, '', v11, v2);
var e1 = graph.insertEdge(parent, null, '', v12, v3);
}
finally
{
// Updates the display
graph.getModel().endUpdate();
}
var button = mxUtils.button('View XML', function()
{
var encoder = new mxCodec();
var node = encoder.encode(graph.getModel());
mxUtils.popup(mxUtils.getPrettyXml(node), true);
});
document.body.insertBefore(button, container.nextSibling);
}
};

How to adjust size of graph node to fit text when using dracula?

I am using dracula for visualizing some graphs. This is a sample html page using dracula:
<html>
<head>
<title>
Testing Dracula
</title>
<script type="text/javascript" src="scripts/vendor/dracula/raphael-min.js">
</script>
<script type="text/javascript" src="scripts/vendor/dracula/dracula_graph.js">
</script>
<script type="text/javascript" src="scripts/vendor/dracula/dracula_graffle.js">
</script>
<script type="text/javascript" src="scripts/vendor/jquery.min.js">
</script>
</head>
<body>
<div id='canvas'>
</div>
<script type="text/javascript">
var render = function(r, n) {
var set = r.set().push(
r.rect(n.point[0] - 30, n.point[1] - 13, 62, 86).attr({
"fill": "#fa8",
"stroke-width": 2,
r: "9px"
})).push(r.text(n.point[0], n.point[1] + 30, n.label).attr({
"font-size": "14px"
})); /* custom tooltip attached to the set */
set.items.forEach(
function(el) {
el.tooltip(r.set().push(r.rect(0, 0, 0, 0).attr({
"fill": "#fec",
"stroke-width": 1,
r: "9px"
})))
});
return set;
};
$(document).ready(function() {
var width = $(document).width() - 20;
var height = $(document).height() - 60;
var g = new Graph();
var data = {
"vertices": ["This is some sample long text.", "A", "B", "C", "D\n some More Sample\n Text", "E"],
"edges": [
[
0, 1],
[
0, 2],
[
0, 3],
[
2, 4],
[
3, 4],
[
4, 5]
]
};
addNodes(data.vertices, g);
addEdges(data.edges, g);
var layouter = new Graph.Layout.Spring(g);
layouter.layout(); /* draw the graph using the RaphaelJS draw implementation */
var renderer = new Graph.Renderer.Raphael('canvas', g, width, height);
renderer.draw();
});
function addNodes(vertices, g) {
jQuery.each(vertices, function(index, text) {
g.addNode(index, {
label: text,
render: render
});
});
}
function addEdges(edges, g) {
jQuery.each(edges, function(index, arr) {
g.addEdge(arr[0], arr[1], {
directed: true
})
});
}
</script>
</body>
</html>
As you can see in the image below, lengthier text in the node flows outside the bounding box. How can I create node so that its dimensions are large enough to fit the contents within its bounds?
You're on the right track here. You can do this in your rendering function. First you add your text element, figure out it's width, use that width when creating the box, then moving the text element to be after the box element. Here's why that fourth step is necessary.
You might modify your code to something like this:
var render = function(r, n) {
var set = r.set().push(
var textbit = r.text(n.point[0], n.point[1] + 30, n.label).attr({ "font-size": "14px" });
var width = $(textbit.node).width() + 10 ;
textbit.remove();
r.rect(n.point[0] - Math.round(width / 2), n.point[1] - 13, width, 86).attr({
"fill": "#fa8",
"stroke-width": 2,
r: "9px"
})).push(r.text(n.point[0], n.point[1] + 30, n.label).attr({
"font-size": "14px"
})); /* custom tooltip attached to the set */
set.items.forEach(
function(el) {
el.tooltip(r.set().push(r.rect(0, 0, 0, 0).attr({
"fill": "#fec",
"stroke-width": 1,
r: "9px"
})))
});
return set;
};
Granted, in this example I'm not actually moving the text element to the back of the DOM, but rather creating a "test" version of the text first just to get the width, then I remove it. Simply moving the element might end up being faster (and perhaps a bit more elegant) but this works
I was able to achieve it in this way.
I added my own render function for this case which handles the size of node on basis of text/label of node.
var render = function (r, n) {
var frame = r.rect(n.point[0] - 3*n.label.length, n.point[1] - 3, 6*n.label.length, 25);
frame.attr({
fill: n.Color, r: n.Radius,
'stroke-width': (n.distance+'px')
});
/* the Raphael set is obligatory, containing all you want to display */
var set = r.set().push(frame,
/* custom objects go here */
r.text(n.point[0], n.point[1] + 10,n.label)
);
return set;
};
This will be dependent on your font size but the formula will remain same.

Categories