How to branch one edge into two using mxGraph? - mxgraph

I didn't find support for connecting one edge to another, thus right now I added a T-piece as a vertex for branching one edge into two edges:
Now I'd like to be able to drag and drop the terminal of one edge onto another edge which should then create and connect a T-piece automatically. As a first step I thought about editing (or rather expanding) getPreviewTerminalState in mxEdgeHandler in order to highlight the target edge when a terminal point is dragged over it. However this feels quiet hacky since I naturally don't want to interfere with a function deep down in the library itself.
Is this the right starting point anyways? I just need a bit of guidance there. Thanks!

Alright by now I've found a solution I can live with and which makes the T-piece obsolete. If someone stumbles upon this issue too, the solution I have looks something like this:
// set edges as valid targets
var graphIsValidTarget = mxGraph.prototype.isValidTarget;
Graph.prototype.isValidTarget = function(cell) {
return (cell && cell.edge) || graphIsValidTarget.apply(this, arguments);
};
// mark edge if another edge's terminal point is dragged over it
var edgeHandlerGetPreviewTerminalState = mxEdgeHandler.prototype.getPreviewTerminalState;
mxEdgeHandler.prototype.getPreviewTerminalState = function(me) {
let ret = edgeHandlerGetPreviewTerminalState.apply(this, arguments);
let edge = me.getCell();
if (edge && edge.edge && this.graph.getSelectionCell() !== edge) {
let state = this.graph.view.getState(edge);
this.marker.setCurrentState(state, me, this.marker.validColor);
ret = state;
} else if (ret == null && this.marker && (!this.error || (this.error && this.error === ''))) {
this.marker.reset();
}
return ret;
};
// snap to edge's midpoint
var graphViewGetFloatingTerminalPoint = mxGraphView.prototype.getFloatingTerminalPoint;
mxGraphView.prototype.getFloatingTerminalPoint = function(edge, start, end, source) {
let res = null;
if (start.edge) {
res = this.graph.getEdgeMidPoint(start);
} else {
res = graphViewGetFloatingTerminalPoint.apply(this, arguments);
}
return res;
};
// returns midpoint of an edge
Graph.prototype.getEdgeMidPoint = function(edge) {
let ret = null;
if (edge && edge.edge) {
let points = this.view.getState(edge).absolutePoints;
let minX = points[0].x < points[1].x ? points[0].x : points[1].x;
let maxX = minX === points[0].x ? points[1].x : points[0].x;
let minY = points[0].y < points[1].y ? points[0].y : points[1].y;
let maxY = minY === points[0].y ? points[1].y : points[0].y;
ret = new mxPoint(minX + (maxX - minX) / 2, minY + (maxY - minY) / 2);
}
return ret;
}

Related

How to check if the model out of the viewer container?

I made an info card and this card will disappear if the viewer is rotated until the model is not visible. I use isNodevisible but it always returns true.
updateInfoCard() {
if (this.infoCard && this.posModel) {
const pos = this.viewer.worldToClient(this.posModel);
console.log(pos);
this.infoCard.style.left = `${Math.floor(
50 + pos.x - this.infoCard.offsetWidth / 2
)}px`;
this.infoCard.style.top = `${Math.floor(
50 + pos.y - this.infoCard.offsetWidth / 2
)}px`;
const id = this.infoCard.dataset.id;
console.log(this.viewer.isNodeVisible(id));
this.infoCard.style.display = this.viewer.isNodeVisible(id)
? "block"
: "none";
}
}
If I understand your question correctly, you'll probably want to do an intersection test between the camera's frustum and the models's bounding box. That can be done like so:
viewer.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, function () {
if (!viewer.model) {
return;
}
const camera = viewer.getCamera();
const matrix = new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
const frustum = new THREE.Frustum().setFromMatrix(matrix);
const bbox = viewer.model.getBoundingBox();
console.log('Model in the view?', frustum.intersectsBox(bbox));
});
And if you only want to check the visibility of a specific element (based on its dbID) of your model, you can compute its bounding box like so:
function objectBounds(model, dbId) {
const tree = model.getInstanceTree();
const frags = model.getFragmentList();
const objectBounds = new THREE.Box3();
tree.enumNodeFragments(dbId, function (fragId) {
const fragBounds = new THREE.Box3();
frags.getWorldBounds(fragId, fragBounds);
objectBounds.union(fragBounds);
}, true);
return objectBounds;
}
The function isNodeVisible returns the visibility status of your node in the scene. If you do something like this.viewer.hide(id, model) your function will return false.
If I well understood what you want to achieve, you want to hide an info card when the associated object is occluded by others objects, so we can't see it from our point of view ?
So I think what you need is to check for occlusion. You can take a look at the checkOcclusion function of this point cloud markup extension made by Philippe Leefsma.
To check for node occlusion, you basically need to raycast from your point of view to the node that you want to check. If you hit something and it's your node, there is no occlusion. If it's not the same node, it's mean that something occlude your node.
checkOcclusion (markup) {
const clientPoint = this.viewer.worldToClient(
markup.point)
const offset = $(this.viewer.container).offset()
const rayCaster = this.pointToRaycaster(
this.viewer.impl.canvas,
this.viewer.impl.camera, {
x: clientPoint.x + offset.left,
y: clientPoint.y + offset.top
})
const hitTest = this.viewer.model.rayIntersect(
rayCaster, true, this.dbIds)
if (hitTest) {
if (hitTest.fragId === markup.fragId) {
const offset = {
x: hitTest.point.x - markup.point.x,
y: hitTest.point.y - markup.point.y,
z: hitTest.point.z - markup.point.z
}
const dist = Math.sqrt(
offset.x * offset.x +
offset.y * offset.y +
offset.z * offset.z)
if (this.options.logOcclusionDist) {
console.log(dist)
}
if (dist < this.options.occlusionDist) {
return false
}
}
return true
}
}

Shuffling objects in JSON ( AngularJS)

I want my questions to be shown in random order. I did it, but it doesn't work properly, sometimes it doesn't loop through all objects and stops and etc.
$scope.move = function (direction) {
var position = $scope.allData.indexOf($scope.currentQ);
$scope.currentQ = $scope.allData[position + direction];
};
$http.get("js/json/questions.json").then(function(response){
$scope.allData = response.data;
$scope.currentQ = $scope.allData[Math.floor(Math.random() * response.data.length)];
});
$scope.answerClick = function(index){
$scope.clicks++;
if(index === $scope.currentQ.answer){
$scope.score++;
$scope.rightwrong = "Right";
$('.right-wrong').css("color", "green");
}
else{
$scope.rightwrong = "Wrong";
$('.right-wrong').css("color", "red");
}
if($scope.clicks === 4){
resultService.score = $scope.score;
$location.path('/finish');
}
$scope.move(+1);
};
Here is the whole app: http://plnkr.co/edit/wTQHOz
In the move method you can get outside the range of the array. Make it wrap around if you move before the first or after the last question:
$scope.currentQ = $scope.allData[(position + direction + $scope.allData.length) % $scope.allData.length];

Speech Synthesis API Volume, Rate, and Pitch don't work

Long story short, Speech Synthesis volume, rate, and pitch don't work. Is there anyone else having this issue and know how to resolve it, or am I alone?
Long story longer:
For me, Speech Synthesis volume, rate, and pitch don't work. Here is my speech function:
function speak(message, voice, callback, volume, rate, pitch, start, lang) {
if (speech) {
window.speechSynthesis.onvoiceschanged = function() {
voices = window.speechSynthesis.getVoices();
var msg = new SpeechSynthesisUtterance();
msg.voice = (typeof voice != "undefined" && voice != 0) ? voices[voice] : voices[0]; // Note: some voices don't support altering params
msg.volume = (typeof volume != "undefined" && volume != 0) ? volume : 1; // 0 to 1
msg.rate = (typeof rate != "undefined" && rate != 0) ? rate : 1; // 0.1 to 10
msg.pitch = (typeof pitch != "undefined" && pitch != 0) ? pitch : 2; //0 to 2
msg.text = message;
msg.lang = (typeof lang != "undefined" && lang != 0) ? lang : "en-US";
msg.onstart = function(event) {
if (typeof start != "undefined" && start != 0) {
start(event);
}
}
msg.onend = function(event) {
console.log(event.elapsedTime);
if (typeof callback != "undefined" && callback != 0) {
callback(event);
}
};
speechSynthesis.speak(msg);
};
}
}
However, when I call speak("Hello", 0, 0, 0.1) it outputs the exact same thing as speak("Hello"). I want to make it output the same thing but softer.
I am currently following http://updates.html5rocks.com/2014/01/Web-apps-that-talk---Introduction-to-the-Speech-Synthesis-API .
This is more of a comment but it could be a typo.
It looks like Speech Synthesis class has a rate property as well.
Be sure to set it on the utterance and not the speech synthesis object.
Incorrect:
speechSynthesis.rate = 2;
speechSynthesis.speak(utterance);
Correct:
utterance.rate = 2;
speechSynthesis.speak(utterance);
For some reason, the parameters will work if you change the language to en-EN.
From what I am seeing, setting these parameters only has an effect when the voice is from a local service.
As you noted, this is probably a case of not all voices supporting the ability to set parameters.
I am trying to do the same thing too. But i managed to make it work by adding a fullstop where I want it to slow down. I know this is not the right way to do it but it is kind of working for me
var u = new SpeechSynthesisUtterance();
u.text = 'Welcome to Handy Mandy. Lets Get Started. Say Handy Mandy.';
//u.lang = 'en-US';
//u.rate = 10;
//u.onend = function(event) { alert('Finished in ' + event.elapsedTime + ' seconds.'); }
speechSynthesis.speak(u);
Before this it

JQuery SVG making objects droppable

I am trying to build a seating reservation web app using SVG. Imagine, I've created rectangles in the svg, which represents an empty seat. I want to allow user to drop an html "image" element on the "rect" to reserve the seat.
However, I couldn't get the droppable to work on the SVG elemnets. Any one has any idea why this is so? Here is the code:
$('#target').svg();
var svg = $("#target").svg('get');
svg.rect(20, 10, 100, 50, 10, 10, { fill: '#666', class: "droppable" });
$('rect')
.droppable({
accept: '.product',
tolerance: 'touch',
drop: function (event, ui) {
alert('df');
}
}
I looked in to the jQuery-ui source and figured out why it wasn't working with SVGs. jQuery thinks they have a width and height of 0px, so the intersection test fails.
I wrapped $.ui.intersect and set the correct size information before passing the arguments through to the original function. There may be more proportion objects that need fixing but the two I did here are enough to fix my :
$.ui.intersect_o = $.ui.intersect;
$.ui.intersect = function(draggable, droppable, toleranceMode) {
//Fix helper
if (draggable.helperProportions && draggable.helperProportions.width === 0 && draggable.helperProportions.height === 0) {
draggable.helperProportionsBBox = draggable.helperProportionsBBox || $(draggable.element).get(0).getBBox();
draggable.helperProportions = draggable.helperProportionsBBox;
}
//Fix droppable
if (droppable.proportions && droppable.proportions.width === 0 && droppable.proportions.height === 0) {
droppable.proportionsBBox = droppable.proportionsBBox || $(droppable.element).get(0).getBBox();
droppable.proportions = droppable.proportionsBBox;
}
return $.ui.intersect_o(draggable, droppable, toleranceMode);
};
With jQuery UI 1.11.4 I had to change Eddie's solution to the following, as draggable.proportions is now a function:
$.ui.intersect_o = $.ui.intersect;
$.ui.intersect = function (draggable, droppable, toleranceMode, event) {
//Fix helper
if (draggable.helperProportions && draggable.helperProportions.width === 0 && draggable.helperProportions.height === 0) {
draggable.helperProportionsBBox = draggable.helperProportionsBBox || $(draggable.element).get(0).getBBox();
draggable.helperProportions = draggable.helperProportionsBBox;
}
if (droppable.proportions && !droppable.proportions().width && !droppable.proportions().height)
if (typeof $(droppable.element).get(0).getBBox === "function") {
droppable.proportionsBBox = droppable.proportionsBBox || $(droppable.element).get(0).getBBox();
droppable.proportions = function () {
return droppable.proportionsBBox;
};
}
return $.ui.intersect_o(draggable, droppable, toleranceMode, event);
};
If you want to restrict the drop on svg elements to hit on visible content only (for example in polygons) you might want to use this addition to Peter Baumann's suggestion:
$.ui.intersect_o = $.ui.intersect;
$.ui.intersect = function (draggable, droppable, toleranceMode, event) {
//Fix helper
if (draggable.helperProportions && draggable.helperProportions.width === 0 && draggable.helperProportions.height === 0) {
draggable.helperProportionsBBox = draggable.helperProportionsBBox || $(draggable.element).get(0).getBBox();
draggable.helperProportions = draggable.helperProportionsBBox;
}
if (droppable.proportions && !droppable.proportions().width && !droppable.proportions().height)
if (typeof $(droppable.element).get(0).getBBox === "function") {
droppable.proportionsBBox = droppable.proportionsBBox || $(droppable.element).get(0).getBBox();
droppable.proportions = function () {
return droppable.proportionsBBox;
};
}
var intersect = $.ui.intersect_o(draggable, droppable, toleranceMode, event);
if (intersect) {
var dropTarget = $(droppable.element).get(0);
if (dropTarget.ownerSVGElement && (typeof (dropTarget.isPointInFill) === 'function') && (typeof (dropTarget.isPointInStroke) === 'function')) {
var x = ( draggable.positionAbs || draggable.position.absolute ).left + draggable.margins.left + draggable.helperProportions.width / 2,
y = ( draggable.positionAbs || draggable.position.absolute ).top + draggable.margins.top + draggable.helperProportions.height / 2,
p = dropTarget.ownerSVGElement.createSVGPoint();
p.x = x;
p.y = y;
p = p.matrixTransform(dropTarget.getScreenCTM().inverse());
if(!(dropTarget.isPointInFill(p) || dropTarget.isPointInStroke(p)))
intersect = false;
}
}
return intersect;
};
In case if anyone has the same question in mind, droppable doesn't work in jquery SVG. So in the end, I did the following to create my own droppable event:
In draggable, set the target currently dragged been dragged to draggedObj ,
In the mouse up event, check if the draggedObj is null, if it's not null, then drop the item according to the current position. ( let me know if you need help on detecting the current position)

A* algorithm works OK, but not perfectly. What's wrong?

This is my grid of nodes:
I'm moving an object around on it using the A* pathfinding algorithm. It generally works OK, but it sometimes acts wrongly:
When moving from 3 to 1, it correctly goes via 2. When going from 1 to 3 however, it goes via 4.
When moving between 3 and 5, it goes via 4 in either direction instead of the shorter way via 6
What can be wrong? Here's my code (AS3):
public static function getPath(from:Point, to:Point, grid:NodeGrid):PointLine {
// get target node
var target:NodeGridNode = grid.getClosestNodeObj(to.x, to.y);
var backtrace:Map = new Map();
var openList:LinkedSet = new LinkedSet();
var closedList:LinkedSet = new LinkedSet();
// begin with first node
openList.add(grid.getClosestNodeObj(from.x, from.y));
// start A*
var curNode:NodeGridNode;
while (openList.size != 0) {
// pick a new current node
if (openList.size == 1) {
curNode = NodeGridNode(openList.first);
}
else {
// find cheapest node in open list
var minScore:Number = Number.MAX_VALUE;
var minNext:NodeGridNode;
openList.iterate(function(next:NodeGridNode, i:int):int {
var score:Number = curNode.distanceTo(next) + next.distanceTo(target);
if (score < minScore) {
minScore = score;
minNext = next;
return LinkedSet.BREAK;
}
return 0;
});
curNode = minNext;
}
// have not reached
if (curNode == target) break;
else {
// move to closed
openList.remove(curNode);
closedList.add(curNode);
// put connected nodes on open list
for each (var adjNode:NodeGridNode in curNode.connects) {
if (!openList.contains(adjNode) && !closedList.contains(adjNode)) {
openList.add(adjNode);
backtrace.put(adjNode, curNode);
}
}
}
}
// make path
var pathPoints:Vector.<Point> = new Vector.<Point>();
pathPoints.push(to);
while(curNode != null) {
pathPoints.unshift(curNode.location);
curNode = backtrace.read(curNode);
}
pathPoints.unshift(from);
return new PointLine(pathPoints);
}
NodeGridNode::distanceTo()
public function distanceTo(o:NodeGridNode):Number {
var dx:Number = location.x - o.location.x;
var dy:Number = location.y - o.location.y;
return Math.sqrt(dx*dx + dy*dy);
}
The problem I see here is the line
if (!openList.contains(adjNode) && !closedList.contains(adjNode))
It may be the case that an adjNode may be easier(shorter) to reach through the current node although it was reached from another node previously which means it is in the openList.
Found the bug:
openList.iterate(function(next:NodeGridNode, i:int):int {
var score:Number = curNode.distanceTo(next) + next.distanceTo(target);
if (score < minScore) {
minScore = score;
minNext = next;
return LinkedSet.BREAK;
}
return 0;
});
The return LinkedSet.BREAK (which acts like a break statement in a regular loop) should not be there. It causes the first node in the open list to be selected always, instead of the cheapest one.