I am trying to parse an GoJS diagram, user can drag different categories from a plate, circle node, rectangle node, triangle. and he can interconnect them in in one direction until reaching the end.
the required function is to parse the graph and give a list of possible paths according the user dependency graph. sample graph is shown here
my function is something like this code:
function collectPaths(y, x) {
var stack = new go.List(go.node);
var coll = new go.List(go.List);
lock = false;
function find(y, x) {
console.log(y.data.name);
y.findNodesInto().each(function (n) {
console.log(y.data.name + " ●▬● " + n.data.name);
if ((n.data.key == x.data.key) && !(lock)) { // success
console.log(n.data.name);
var path = stack.copy();
path.reverse();
coll.add(path);
} else if((n.data.key !=x.data.key) && lock){
// stack.add(n);
console.log(n.data.name);
if (n.data.category === "triangle") {
pp = pp.findNodesInto();
var it = pp.iterator;
var m = new go.Map(go.node, "number");
lock = true;
while (it.next()) {
m.pop(it.value,it.value);
stack.add(it.value);
console.log(it.value.data.name);
find(it.value, x);
}
var tempList=go.list(go.node);
tempList.each(function (pn) {
pn = tempList.pop();
if (!"undefined") {
stack.add(parent);
find(parent, x);
// stack.add(pn);
console.log(pn.data.name);
} else {
pn = tempList.pop();
find(pn, x);
}
});
} else {
console.log(n.data.name);
stack.add(n);
find(n, x);
stack.removeAt(stack.count - 1);
}
}
lock = false;
});
} // end of full stack collection
find(y, x);
return coll;
}
but the function doesn't give the required output.
expected output like this: for the figure attached as follows:
N30 – N40 – N10
N1 -N2-N3-N4-N10
N5-N6-N9-N10
N5-N10
N7-N8-N10
N7-N8-N11-N10
What I can do ?
The sample https://gojs.net/latest/samples/distances.html demonstrates how to find all paths between any pair of nodes. You want to use the collectAllPaths function -- you can delete the functions involved with creating a random graph or with finding distances between nodes or with helping the user select the start and end nodes interactively.
Related
I am trying to customize the Split node in Node-Red in a way to send the next message only when the first arrives to the Join node; as I am doing some processing in between, and would like to process each msg separately before joining them.
So I have cloned the Split node from Node-Red project, and at the part where the splitting of an array happens; I register listeners to events (random IDs generated by the original Split node).
else if (Array.isArray(msg.payload)) { // then split array into messages
msg.parts.type = 'array';
var count = msg.payload.length / node.arraySplt;
if (Math.floor(count) !== count) {
count = Math.ceil(count);
}
msg.parts.count = count;
var pos = 0;
var data = msg.payload;
msg.parts.len = node.arraySplt;
for (let i = 0; i < count; i++) {
msg.payload = data.slice(pos, pos + node.arraySplt);
if (node.arraySplt === 1) {
msg.payload = msg.payload[0];
}
msg.parts.index = i;
pos += node.arraySplt;
if (i === 0) {
send(RED.util.cloneMessage(msg));
continue;
}
let eventName = msg.parts.id + '-' + i;
addHandler(eventName, send, msg);
}
done();
}
My handler function
function addHandler(eventName, send, msg) {
RED.events.addListener(eventName, () => {
send(RED.util.cloneMessage(msg));
});
}
And at the Join node (which is in the same js file)
// commented below is the original code of the join function
// if ((tcnt > 0 && group.currentCount > tcnt) || msg.hasOwnProperty('complete')) {
// completeSend(partId);
// return;
// } else
if (msg.parts.index <= (msg.parts.count - 2)) {
let eventName = msg.parts.id + '-' + (parseInt(msg.parts.index) + 1);
RED.events.emit(eventName);
} else if (msg.parts.index >= (msg.parts.count - 1)) {
completeSend(partId);
return;
}
However, this would send the first msg (as I directly send it, and not through an event), and the last msg only; it would skip whatever in between.
Modifying the split node is the wrong way to do this.
There are rate limiting nodes that will do this for you. e.g. https://flows.nodered.org/node/node-red-contrib-semaphore
Or you can use the delay node with it's release function
I am trying to look for matching cells in column R,T,and V in the reference table with column C in the main sheet. If there is a match then copy the assigned number in Q,S, and U and past it in column B next to the its match.
I have done the example manually.
Suggestion
Perhaps you can try this sample script:
Script [UPDATED]
function findMatch() {
var sh = SpreadsheetApp.getActiveSpreadsheet();
var lastRow = sh.getDataRange().getLastRow();
var reference = [];
var row = 1;
var data = sh.getRange("Q1:V"+lastRow).getDisplayValues().filter(function(x) {
return (x.join('').length !== 0);
});
sh.getRange("C1:C"+lastRow).getDisplayValues().forEach( x => {
reference.push([fixForUsage(x),row]);
row += 1;
});
reference.forEach(aisle => {
if(aisle[0] == "" || aisle[0].toString().toLowerCase().includes("aisle"))return;
data.forEach(match => {
var currentData = match[1] +"-"+match[3]+"-"+match[5];
if(currentData.includes(aisle[0])){
//get its number
if(match[1] == ""){
if(match[3] == ""){
if(match[5] == ""){
}else{
//if not empty
Logger.log("Find \""+aisle[0]+"\" from sheet row#"+aisle[1]+"\nRESULT:"+"\nFound a match on these data!: "+match+"\nHeader: \'"+data[0][4]+"\'\nNumber: "+match[4]);
pasteData(sh,aisle[1], data[0][4], match[4]);
}
}else{
//if not empty
Logger.log(+"Find \""+aisle[0]+"\" from sheet row#"+aisle[1]+"\nRESULT:"+"\nFound a match on these data!: "+match+"\nHeader: \'"+data[0][2]+"\'\nNumber: "+match[2]);
pasteData(sh,aisle[1],data[0][2],match[2]);
}
}else{
//if not empty
Logger.log("Find \""+aisle[0]+"\" from sheet row#"+aisle[1]+"\nRESULT:"+"\nFound a match on these data!: "+match+"\nHeader: \'"+data[0][0]+"\'\nNumber: "+match[0]);
pasteData(sh,aisle[1],data[0][0],match[0]);
}
}
});
});
}
function pasteData(sh,row, colAData, colBData){
sh.getRange("B"+row).setValue(colBData);
sh.getRange("A"+row).setValue(colAData);
}
function fixForUsage(x){ //This is to let the code know that e.q. 53-7 is 53-07
var part = x.toString().split("-");
if(part.length == 2 && part[1].length == 1){
return part[0]+"-0"+part[1];
}else{
return x;
}
}
Sample Sheet
Columns A, B, C:
Columns Q, R, S, T, U, V:
Sample Demonstration
After running the script from the Apps Script editor:
Apps Script editor log results for review:
Here is another solution (updated):
function myFunction() {
var sh = SpreadsheetApp.getActiveSheet();
// get all data
var data = sh.getRange('q2:v16').getDisplayValues();
var cols_QR = data.map(x => ({'crane': 'C5-1', 'seq': x[0], 'aisle': x[1]}));
var cols_ST = data.map(x => ({'crane': 'C5-4', 'seq': x[2], 'aisle': x[3]}));
var cols_UV = data.map(x => ({'crane': 'C5-2', 'seq': x[4], 'aisle': x[5]}));
data = [...cols_QR, ...cols_ST, ...cols_UV];
data = data.filter(x => x.aisle != '');
data.forEach((x,i) => data[i].aisle = fix(x.aisle)); // <--- new line
// create the object 'aisles'
var aisles = {}
for (let obj of data) aisles[obj.aisle] = {'crane': obj.crane, 'seq': obj.seq}
// get target range and target data
var target_range = sh.getRange('a2:c' + sh.getLastRow());
var target_data = target_range.getDisplayValues();
// fill target range with info from the 'aisles' object
for (let row in target_data) {
try {
let orig_key = target_data[row][2]; // <--- updated line
let key = fix(orig_key); // <--- new line
target_data[row] = [ aisles[key].crane, aisles[key].seq, orig_key ]; // <--- updated line
} catch(e) {}
}
// fill the target range with updated data
target_range.setValues(target_data);
}
// function to convert '10-1 1/4' --> '10-01', '8-0' --> '08-00', etc
function fix(key) {
return key.split(' ')[0]
.split('-').map(x => x.length == 1 ? '0' + x : x)
.join('-').slice(0,5);
}
Initial data:
Results:
Sheet
I decided don't add in my variant the function that makes true this: '53-7' == '53-07' and '11' == '11-0', etc, because I think it's a rather bad and error-prone idea. It will silently hide many errors in your data. But I can add it if you want.
Update
The function can be something like this:
function fix(key) {
return key.split(' ')[0]
.split('-').map(x => x.length == 1 ? '0' + x : x)
.join('-').slice(0,5);
}
// test
const keys = ['10-1 1/4', '11-0', '9-11 1/2', '8-0', '53-06', '52-7', '53-07', '104', '8-02-S', '08-01'];
keys.forEach(key => console.log(fix(key) + ' <--- ' + key));
I've added the function fix() and several lines to my original code.
But actually functions like this can cause additional troubles. For example I have no idea what means '104'? If it should be '10-04'? Or may be '01-04'? Or something else? You need be well prepared if you decide to play the risky game.
Note: you can change orig_key to key in this line:
target_data[row] = [ aisles[key].crane, aisles[key].seq, orig_key ];
This way you will get the 'fixed' values in the target table instead of original ones.
I am trying to create flowing stacked area chart using d3.js. I have the graph working with a specific range. I am hitting the service every 10ms and getting all data and then triggering an event to start the graph. However my graph works only works for 30mins after that I am trying to reset the interval. When I am trying to do that it there is a jerk in the graph and somehow it breaks. I am not sure if I am doing it the right way. Here is code to look at.
var numbers = [];
var values = [];
var flag = false;
var fd;
var td;
//Calling the dates service
d3.json('/api/dates', function(error,data) {
var dates = data;
if(data != null){
fd = new Date(data.start);
td = new Date(data.end);
var cnt = 0;
var startGraph = function(){
if (fd > td) {
console.log(" start Date is greater than end Date");
clearInterval(interval);
flag = true;
$('.wrapper').trigger('newPoint');
return;
}
var fdt = fd.toISOString();
var tdt = new Date(fd.setMinutes(fd.getMinutes() + 30)).toISOString();
//Calling service to get the values for stacked area chart
d3.json("/api/service?start=" +fdt+ "&end=" +tdt, function(error,result) {
if(result != null){
numbers = numbers.concat(flightInfo.numbers);
values[values.length] = flightInfo.values;
}
});
cnt++;
}
function pushPoint(){
var cnt=0;
var interval = setInterval(function(){
if(cnt!=values.length)
{
tick(values[cnt]);
cnt++;}
else
clearInterval(interval);
},400);
}
//Calling the Processing Dates API to pull the data for area chart
var interval = setInterval(startGraph,10);
}
});
$(document).ready(function () {
stackGraph(); // this is another js file
var cnt=0;
//Pushing new point
$('.wrapper').on('newPoint', function(){
if(flag){
if(cnt!=values.length){
tick(values[cnt]);
cnt++;
}
}
});
});
Is there actually a line break on the line with your return statement here?
$('.background').trigger('newPoint');
return //is there actually a line break here?
setTimeout(startGraph,10);
If so, you aren't going to execute the next line.
Why doesn't a Javascript return statement work when the return value is on a new line?
hello i'd like to ask some javascript code in finding path using algorithm A*. the path will have source node and target node, and here's the example code
do{
var cursor = nodes.node1 //this is source node
for (var i in GexfJS.graph.edgeList) { // search all edgeList on graph
var _e = GexfJS.graph.edgeList[i] //each list has variable _e
var position = []; // where i put the pointer
if ( _e.source == cursor ) { //when some edge source is match from the cursor(source node/initial node)
var _n = GexfJS.graph.nodeList[_e.target]; // edge target from the node which match with the cursor
_str += 'Found '; // just give string if it's works
position.push(_e.target); // for pointer?
cursor = _e.target // go to the next node, but how can i move to previos node?
}
}
} while(cursor!=nodes.node2); //until find the target node
the problem is im using position.push and it is array but i can't implement the pointer, if its has move next or move previous when finding path to the target node. thanks
maybe you don't need push position.push(_e.target); , just let the cursor in to the end , after the cursor hasn't more target , you can initial the cursor to back to the first node and don't forget each node you visit has to be set to be true
i have the answer for this, initial the cursor from the first node until the target node and don't forget each node you visit has to be set to be true if the node has visited, then go back again try to finding the first edges from the nodes, if the node hasn't true go to the next node, if the node has true find other node. here's my example code
pathed={};
ketemu=false;
cursor1="";
if(path==true){
while(ketemu!=true){
var cursor = nodes.node1;
var lala = new Array();
var visit = new Array();
var j = 0;
for (var i in GexfJS.graph.edgeList) { // relasi yang dituju
var _e = GexfJS.graph.edgeList[i];
if ( _e.source == cursor ) {
var _n = GexfJS.graph.nodeList[_e.target];
if(pathed[_n.label] != true){
if(_e.target==nodes.node2){
cursor = _e.target;
cursor1 = _e.target;
lala[j] = cursor;
visit[j] = cursor;
j++;
ketemu=true;
break;
}
if(_e.target !=nodes.node2){
cursor = _e.target;
lala[j] = cursor;
visit[j] = cursor;
j++;
}
//alert(cursor);
//alert(lala[j]);
}
}
//else if(cursor1==_e.target){
//cursor = nodes.node1;
//alert(cursor);
//}
}
if(ketemu!=true){
i=0;
for(var j=lala.length-1;j>=0;j--){
var test = lala.length-1;
var ujung = lala[test];
var koyong = nodes.node1;
i++;
}
ujung = GexfJS.graph.nodeList[ujung];
pathed[ujung.label] = true;
cursor = koyong;
}
}
for(var k=0;k<visit.length;k++){
var visitednya = visit[k];
var _n = GexfJS.graph.nodeList[visitednya];
_str += '<li><div class="smallpill" style="background: ' + _n.color.base +'"></div>' + _n.label + '' + ( GexfJS.params.showEdgeWeight && _e.weight ? ' [' + _e.weight + ']' : '') + '</li>';
}
visit=[];
//end
}
thanks
I'm plotting about 15 named metrics on cubism.js. I'm seeing the stat values being return as metrics. Also, if I scroll over the visualization, the ruler is showing me the proper values. However, its just not drawing the graph. When I inspect the horizon div, this is what I see:
<span class="value" style=""></span>
As you can see, the span is empty. It should look something like this:
<span class="value" style="">"10"</span>
Here's the code I'm using to create the metrics
var context = cubism.context()
.serverDelay(0)
.clientDelay(0)
.step(1*5*60)
.size(1440);
//aggregate 'metrics'. Each component has a 'metric' which contains its stats over time
for(model in models){
var attributes = models[model].attributes;
var name = attributes['name'];
var curContext = getMetric(name, attributes);
statsList.push(curContext);
}
function getMetric(name, attributes){
return context.metric(function(start, stop, step, callback){
var statValues = [];
//convert start and stop to milliseconds
start = +start;
stop = +stop;
//depending on component type, push the correct attribute
var type = attributes['type'];
var stat = attributes['stat'];
//cubism expects metrics over time stored in 'slots'. these slots are indexed
//using 'start' and 'stop'
//if the metric already exists, we store the stats in each of the slots
if(initializedMetrics[name]){
while(start < stop){
start+= step;
statValues.push(stat);
}
} else{
//if the metric is not initialized, we fill the preceeding slots with NaN
initializedMetrics[name] = true;
while (start < (stop - step)) {
start += step;
statValues.push(NaN);
}
statValues.push(stat);
}
if(stat < min){
min = stat;
}
if(stat > max){
max = stat;
}
//console.log("pushing stat", name, stat)
callback(null, statValues);
}, name);
}
d3.select(this.loc).selectAll(".axis")
.data(["top", "bottom"])
.enter().append("div")
.attr("class", function(d) { return d + " axis"; })
.each(function(d) { d3.select(this).call(context.axis().ticks(12).orient(d)); });
d3.select(this.loc).append("div")
.attr("class", "rule")
.call(context.rule());
d3.select(this.loc).selectAll(".horizon")
.data(statsList)
.enter().insert("div", ".bottom")
.attr("class", "horizon")
.call(context.horizon().height(20));
var size = this.getSize();
context.on("focus", function(i) {
d3.selectAll(".value").style("right", i == null ? null : context.size() - i + "px");
});
},
getSize: function () {
return $(this.loc).width();
},
To phrase this as a single question, at what step is the 'value' in the horizon div set?
Just to clarify, I am seeing the named horizon divs created. Just no timeseries path being drawn.