Replacing nodes with new calculations

This commit is contained in:
Hubert Sokołowski
2021-04-10 22:29:31 +02:00
parent f7224c5fd9
commit f7e2cd47e7
22 changed files with 1632 additions and 71 deletions

560
public/algorithms/abc.js Normal file
View File

@@ -0,0 +1,560 @@
window.counter = 0;
//let list_obj=[];
window.list_obj = [];
//let counter=0;
export var dt = (function () {
//debugger;
/**
* Creates an instance of DecisionTree
*
* @constructor
* @param builder - contains training set and
* some configuration parameters
*/
function DecisionTree(builder) {
this.root = buildDecisionTree({
trainingSet: builder.trainingSet,
ignoredAttributes: arrayToHashSet(builder.ignoredAttributes),
//ignoredAttributes: builder.ignoredAttributes,
categoryAttr: builder.categoryAttr || 'category',
minItemsCount: builder.minItemsCount, //|| 4,
entropyThrehold: builder.entropyThrehold, // || 0.1,
maxTreeDepth: builder.maxTreeDepth, // || 30
});
//console.log(builder.ignoredAttributes)
}
DecisionTree.prototype.predict = function (item) {
return predict(this.root, item);
};
/**
* Creates an instance of RandomForest
* with specific number of trees
*
* @constructor
* @param builder - contains training set and some
* configuration parameters for
* building decision trees
*/
function RandomForest(builder, treesNumber) {
this.trees = buildRandomForest(builder, treesNumber);
}
RandomForest.prototype.predict = function (item) {
return predictRandomForest(this.trees, item);
};
/**
* Transforming array to object with such attributes
* as elements of array (afterwards it can be used as HashSet)
*/
function arrayToHashSet(array) {
var hashSet = {};
if (array) {
for (var i in array) {
var attr = array[i];
hashSet[attr] = true;
}
}
return hashSet;
}
/**
* Calculating how many objects have the same
* values of specific attribute.
*
* @param items - array of objects
*
* @param attr - variable with name of attribute,
* which embedded in each object
*/
function countUniqueValues(items, attr) {
var counter = {};
// detecting different values of attribute
for (var i = items.length - 1; i >= 0; i--) {
// items[i][attr] - value of attribute
counter[items[i][attr]] = 0;
}
// counting number of occurrences of each of values
// of attribute
for (var j = items.length - 1; j >= 0; j--) {
counter[items[j][attr]] += 1;
}
return counter;
}
/**
* Calculating entropy of array of objects
* by specific attribute.
*
* @param items - array of objects
*
* @param attr - variable with name of attribute,
* which embedded in each object
*/
function entropy(items, attr) {
// counting number of occurrences of each of values
// of attribute
var counter = countUniqueValues(items, attr);
var entropy = 0;
var p;
for (var i in counter) {
p = counter[i] / items.length;
entropy += -p * Math.log(p);
}
return entropy;
}
/**
* Splitting array of objects by value of specific attribute,
* using specific predicate and pivot.
*
* Items which matched by predicate will be copied to
* the new array called 'match', and the rest of the items
* will be copied to array with name 'notMatch'
*
* @param items - array of objects
*
* @param attr - variable with name of attribute,
* which embedded in each object
*
* @param predicate - function(x, y)
* which returns 'true' or 'false'
*
* @param pivot - used as the second argument when
* calling predicate function:
* e.g. predicate(item[attr], pivot)
*/
function split(items, attr, predicate, pivot) {
var match = [];
var notMatch = [];
var item, attrValue;
for (var i = items.length - 1; i >= 0; i--) {
item = items[i];
attrValue = item[attr];
if (predicate(attrValue, pivot)) {
match.push(item);
} else {
notMatch.push(item);
}
}
return {
match: match,
notMatch: notMatch,
};
}
/**
* Finding value of specific attribute which is most frequent
* in given array of objects.
*
* @param items - array of objects
*
* @param attr - variable with name of attribute,
* which embedded in each object
*/
function mostFrequentValue(items, attr) {
// counting number of occurrences of each of values
// of attribute
var counter = countUniqueValues(items, attr);
var mostFrequentCount = 0;
var mostFrequentValue;
for (var value in counter) {
if (counter[value] > mostFrequentCount) {
mostFrequentCount = counter[value];
mostFrequentValue = value;
}
}
return mostFrequentValue;
}
var predicates = {
'==': function (a, b) {
return a === b;
},
'>=': function (a, b) {
return a >= b;
},
};
/**
* Function for building decision tree
*/
function buildDecisionTree(builder) {
var trainingSet = builder.trainingSet;
var minItemsCount = builder.minItemsCount;
var categoryAttr = builder.categoryAttr;
var entropyThrehold = builder.entropyThrehold;
var maxTreeDepth = builder.maxTreeDepth;
var ignoredAttributes = builder.ignoredAttributes;
let _quality = 0;
if (maxTreeDepth === 0 || trainingSet.length <= minItemsCount) {
let _category = mostFrequentValue(trainingSet, categoryAttr);
let _positiveCounter = 0;
let _hide = 0;
//console.log(trainingSet);
trainingSet.forEach(element => {
if (element[categoryAttr] === _category) _positiveCounter++;
});
let _negativeCounter = trainingSet.length - _positiveCounter;
_quality = _positiveCounter / trainingSet.length;
if (_quality !== 1) _hide = 25;
_quality = _quality * 100;
_quality = _quality.toFixed(2);
// restriction by maximal depth of tree
// or size of training set is to small
// so we have to terminate process of building tree
return {
category: _category,
quality: _quality,
matchedCount: _positiveCounter,
notMatchedCount: _negativeCounter,
hide: _hide,
};
}
//console.log(categoryAttr);
var initialEntropy = entropy(trainingSet, categoryAttr);
console.log('initialEntropy ' + initialEntropy);
if (initialEntropy <= entropyThrehold) {
let _category = mostFrequentValue(trainingSet, categoryAttr);
let _positiveCounter = 0;
let _hide = 0;
//console.log(trainingSet);
trainingSet.forEach(element => {
if (element[categoryAttr] === _category) _positiveCounter++;
});
let _negativeCounter = trainingSet.length - _positiveCounter;
_quality = _positiveCounter / trainingSet.length;
if (_quality !== 1) _hide = 25;
_quality = _quality * 100;
_quality = _quality.toFixed(2);
// entropy of training set too small
// (it means that training set is almost homogeneous),
// so we have to terminate process of building tree
return {
category: _category,
quality: _quality,
matchedCount: _positiveCounter,
notMatchedCount: _negativeCounter,
hide: _hide,
};
}
// used as hash-set for avoiding the checking of split by rules
// with the same 'attribute-predicate-pivot' more than once
var alreadyChecked = {};
// this variable expected to contain rule, which splits training set
// into subsets with smaller values of entropy (produces informational gain)
var bestSplit = { gain: 0 };
var pivot;
var predicateName;
var attrPredPivot;
var predicate;
var currSplit;
var matchEntropy;
var notMatchEntropy;
var newEntropy;
var currGain;
if (window.isChange) {
let attr = window.CurrentAttribute;
// let the value of current attribute be the pivot
pivot = window.inputValue;
//console.log(attr +" "+ pivot)
if (!isNaN(pivot)) {
pivot = parseFloat(pivot);
}
// pick the predicate
// depending on the type of the attribute value
// var predicateName;
if (typeof pivot == 'number') {
predicateName = '>=';
} else {
// there is no sense to compare non-numeric attributes
// so we will check only equality of such attributes
predicateName = '==';
}
attrPredPivot = attr + predicateName + pivot;
if (alreadyChecked[attrPredPivot]) {
// skip such pairs of 'attribute-predicate-pivot',
// which been already checked
//continue;
}
alreadyChecked[attrPredPivot] = true;
predicate = predicates[predicateName];
// splitting training set by given 'attribute-predicate-value'
currSplit = split(trainingSet, attr, predicate, pivot);
//console.log(currSplit.match)
//console.log(currSplit.notMatch)
// calculating entropy of subsets
matchEntropy = entropy(currSplit.match, categoryAttr);
notMatchEntropy = entropy(currSplit.notMatch, categoryAttr);
// calculating informational gain
newEntropy = 0;
newEntropy += matchEntropy * currSplit.match.length;
newEntropy += notMatchEntropy * currSplit.notMatch.length;
newEntropy /= trainingSet.length;
currGain = initialEntropy - newEntropy;
console.log('CURRENT GAIN ' + currGain);
if (currGain > bestSplit.gain) {
// remember pairs 'attribute-predicate-value'
// which provides informational gain
bestSplit = currSplit;
bestSplit.predicateName = predicateName;
bestSplit.predicate = predicate;
bestSplit.attribute = attr;
bestSplit.pivot = pivot;
bestSplit.gain = currGain;
}
window.isChange = false;
} else {
//delete space from property in object
// let ignore=""
// if(!(Object.entries(ignoredAttributes).length === 0 && ignoredAttributes.constructor === Object)){
// ignore = Object.keys(ignoredAttributes)
// ignore = ignore[0].substring(0, ignore[0].length - 1);
// console.log(ignore)
// }
//console.log("Liczy")
for (var i = trainingSet.length - 1; i >= 0; i--) {
var item = trainingSet[i];
// iterating over all attributes of item
for (var attr in item) {
//console.log(ignoredAttributes)
//if(ignoredAttributes[attr]===true) console.log("równe")
if (attr === categoryAttr || ignoredAttributes[attr]) {
//if ((attr === categoryAttr) || ignore===attr) {
continue;
}
// let the value of current attribute be the pivot
pivot = item[attr];
if (!isNaN(pivot)) {
pivot = parseFloat(pivot);
}
// pick the predicate
// depending on the type of the attribute value
//var predicateName;
if (typeof pivot == 'number') {
//console.log('is number ' + pivot + ' ' + typeof pivot)
predicateName = '>=';
} else {
//console.log('is not number ' + pivot + ' ' + typeof pivot)
// there is no sense to compare non-numeric attributes
// so we will check only equality of such attributes
predicateName = '==';
}
attrPredPivot = attr + predicateName + pivot;
if (alreadyChecked[attrPredPivot]) {
// skip such pairs of 'attribute-predicate-pivot',
// which been already checked
continue;
}
alreadyChecked[attrPredPivot] = true;
predicate = predicates[predicateName];
// splitting training set by given 'attribute-predicate-value'
currSplit = split(trainingSet, attr, predicate, pivot);
////console.log(currSplit)
// calculating entropy of subsets
matchEntropy = entropy(currSplit.match, categoryAttr);
notMatchEntropy = entropy(currSplit.notMatch, categoryAttr);
////console.log(bestSplit.gain)
// calculating informational gain
newEntropy = 0;
newEntropy += matchEntropy * currSplit.match.length;
newEntropy += notMatchEntropy * currSplit.notMatch.length;
newEntropy /= trainingSet.length;
currGain = initialEntropy - newEntropy;
//console.log("CURRENT GAIN 2"+currGain)
if (currGain > bestSplit.gain) {
// remember pairs 'attribute-predicate-value'
// which provides informational gain
bestSplit = currSplit;
bestSplit.predicateName = predicateName;
bestSplit.predicate = predicate;
bestSplit.attribute = attr;
bestSplit.pivot = pivot;
bestSplit.gain = currGain;
}
}
}
}
console.log(bestSplit.gain);
if (!bestSplit.gain) {
let _category = mostFrequentValue(trainingSet, categoryAttr);
let _positiveCounter = 0;
let _hide = 0;
//console.log(trainingSet);
trainingSet.forEach(element => {
if (element[categoryAttr] === _category) _positiveCounter++;
});
let _negativeCounter = trainingSet.length - _positiveCounter;
_quality = _positiveCounter / trainingSet.length;
if (_quality !== 1) _hide = 25;
_quality = _quality * 100;
_quality = _quality.toFixed(2);
// can't find optimal split
return {
category: _category,
quality: _quality,
matchedCount: _positiveCounter,
notMatchedCount: _negativeCounter,
hide: _hide,
};
}
if (window.isChange) {
bestSplit.pivot = window.inputValue;
window.isChange = false;
}
let array = bestSplit.match.concat(bestSplit.notMatch);
window.list_obj.push({
id: window.counter,
match: bestSplit.match,
notMatch: bestSplit.notMatch,
full: array,
});
window.counter++;
//console.log(window.list_obj)
// building subtrees
builder.maxTreeDepth = maxTreeDepth - 1;
builder.trainingSet = bestSplit.match;
var matchSubTree = buildDecisionTree(builder);
builder.trainingSet = bestSplit.notMatch;
var notMatchSubTree = buildDecisionTree(builder);
return {
attribute: bestSplit.attribute,
predicate: bestSplit.predicate,
predicateName: bestSplit.predicateName,
pivot: bestSplit.pivot,
match: matchSubTree,
notMatch: notMatchSubTree,
matchedCount: bestSplit.match.length,
notMatchedCount: bestSplit.notMatch.length,
};
}
/**
* Classifying item, using decision tree
*/
function predict(tree, item) {
var attr, value, predicate, pivot;
// Traversing tree from the root to leaf
while (true) {
if (tree.category) {
// only leafs contains predicted category
return tree.category;
}
attr = tree.attribute;
value = item[attr];
predicate = tree.predicate;
pivot = tree.pivot;
// move to one of subtrees
if (predicate(value, pivot)) {
tree = tree.match;
} else {
tree = tree.notMatch;
}
}
}
/**
* Building array of decision trees
*/
function buildRandomForest(builder, treesNumber) {
var items = builder.trainingSet;
// creating training sets for each tree
var trainingSets = [];
for (var t = 0; t < treesNumber; t++) {
trainingSets[t] = [];
}
for (var i = items.length - 1; i >= 0; i--) {
// assigning items to training sets of each tree
// using 'round-robin' strategy
var correspondingTree = i % treesNumber;
trainingSets[correspondingTree].push(items[i]);
}
// building decision trees
var forest = [];
for (var tt = 0; tt < treesNumber; tt++) {
builder.trainingSet = trainingSets[tt];
var tree = new DecisionTree(builder);
forest.push(tree);
}
return forest;
}
/**
* Each of decision tree classifying item
* ('voting' that item corresponds to some class).
*
* This function returns hash, which contains
* all classifying results, and number of votes
* which were given for each of classifying results
*/
function predictRandomForest(forest, item) {
var result = {};
for (var i in forest) {
var tree = forest[i];
var prediction = tree.predict(item);
result[prediction] = result[prediction] ? result[prediction] + 1 : 1;
}
return result;
}
var exports = {};
exports.DecisionTree = DecisionTree;
exports.RandomForest = RandomForest;
return exports;
})();
export function getListObj() {
return window.list_obj;
}
export function confusionMatrix(array) {}

View File

@@ -0,0 +1,413 @@
// @ts-check
/**
* @typedef {Object} DecisionTreeBuilder
* @property {Array<Object>} trainingSet
* @property {Array<string>} allAttributes
* @property {Array<string>} allClasses
* @property {number} minItemsCount
* @property {string} categoryAttr
* @property {number} entropyThrehold
* @property {number} maxTreeDepth
* @property {Array<string>} ignoredAttributes
*/
/**
* @param {DecisionTreeBuilder} _builder
* @param {boolean} isChanged
*/
//TSP
function buildDecisionTreeC45(
_builder,
isChanged = false,
changedAttribute1 = null,
changedAttribute2 = null
) {
//debugger;
const builder = { ..._builder };
const {
trainingSet,
minItemsCount,
categoryAttr,
entropyThrehold,
maxTreeDepth,
ignoredAttributes,
} = builder;
let _quality = 0;
if (maxTreeDepth === 0 || trainingSet.length <= minItemsCount) {
let _category = mostFrequentValue(trainingSet, categoryAttr);
let _positiveCounter = 0;
let _hide = 0;
//console.log(trainingSet);
trainingSet.forEach(element => {
if (element[categoryAttr] === _category) _positiveCounter++;
});
let _negativeCounter = trainingSet.length - _positiveCounter;
_quality = _positiveCounter / trainingSet.length;
if (_quality !== 1) _hide = 25;
_quality = _quality * 100;
// restriction by maximal depth of tree
// or size of training set is to small
// so we have to terminate process of building tree
return {
category: _category,
quality: _quality,
matchedCount: _positiveCounter,
notMatchedCount: _negativeCounter,
trainingSet2: trainingSet.map(x => x[categoryAttr]),
};
}
//console.log(categoryAttr);
var initialEntropy = entropy(trainingSet, categoryAttr);
console.log('initialEntropy ' + initialEntropy);
if (initialEntropy <= entropyThrehold) {
let _category = mostFrequentValue(trainingSet, categoryAttr);
let _positiveCounter = 0;
let _hide = 0;
//console.log(trainingSet);
trainingSet.forEach(element => {
if (element[categoryAttr] === _category) _positiveCounter++;
});
let _negativeCounter = trainingSet.length - _positiveCounter;
_quality = _positiveCounter / trainingSet.length;
if (_quality !== 1) _hide = 25;
_quality = _quality * 100;
// entropy of training set too small
// (it means that training set is almost homogeneous),
// so we have to terminate process of building tree
return {
category: _category,
quality: _quality,
matchedCount: _positiveCounter,
notMatchedCount: _negativeCounter,
trainingSet2: trainingSet.map(x => x[categoryAttr]),
};
}
// used as hash-set for avoiding the checking of split by rules
// with the same 'attribute-predicate-pivot' more than once
var alreadyChecked = {};
// this variable expected to contain rule, which splits training set
// into subsets with smaller values of entropy (produces informational gain)
var bestSplit = { gain: 0 };
var pivot;
var predicateName;
var attrPredPivot;
var predicate;
var currSplit;
var matchEntropy;
var notMatchEntropy;
var newEntropy;
var currGain;
if (isChanged) {
let attr = changedAttribute1;
// let the value of current attribute be the pivot
pivot = changedAttribute2;
//console.log(attr +" "+ pivot)
if (!isNaN(pivot)) {
pivot = parseFloat(pivot);
}
// pick the predicate
// depending on the type of the attribute value
// var predicateName;
if (typeof pivot == 'number') {
predicateName = '>=';
} else {
// there is no sense to compare non-numeric attributes
// so we will check only equality of such attributes
predicateName = '==';
}
attrPredPivot = attr + predicateName + pivot;
if (alreadyChecked[attrPredPivot]) {
// skip such pairs of 'attribute-predicate-pivot',
// which been already checked
//continue;
}
alreadyChecked[attrPredPivot] = true;
predicate = predicates[predicateName];
// splitting training set by given 'attribute-predicate-value'
currSplit = split(trainingSet, attr, predicate, pivot);
//console.log(currSplit.match)
//console.log(currSplit.notMatch)
// calculating entropy of subsets
matchEntropy = entropy(currSplit.match, categoryAttr);
notMatchEntropy = entropy(currSplit.notMatch, categoryAttr);
// calculating informational gain
newEntropy = 0;
newEntropy += matchEntropy * currSplit.match.length;
newEntropy += notMatchEntropy * currSplit.notMatch.length;
newEntropy /= trainingSet.length;
currGain = initialEntropy - newEntropy;
console.log('CURRENT GAIN ' + currGain);
if (currGain > bestSplit.gain) {
// remember pairs 'attribute-predicate-value'
// which provides informational gain
bestSplit = currSplit;
bestSplit.predicateName = predicateName;
bestSplit.predicate = predicate;
bestSplit.attribute = attr;
bestSplit.pivot = pivot;
bestSplit.gain = currGain;
}
isChanged = false;
} else {
//delete space from property in object
// let ignore=""
// if(!(Object.entries(ignoredAttributes).length === 0 && ignoredAttributes.constructor === Object)){
// ignore = Object.keys(ignoredAttributes)
// ignore = ignore[0].substring(0, ignore[0].length - 1);
// console.log(ignore)
// }
//console.log("Liczy")
for (var i = trainingSet.length - 1; i >= 0; i--) {
var item = trainingSet[i];
// iterating over all attributes of item
for (var attr in item) {
//console.log(ignoredAttributes)
//if(ignoredAttributes[attr]===true) console.log("równe")
if (attr === categoryAttr || ignoredAttributes[attr]) {
//if ((attr === categoryAttr) || ignore===attr) {
continue;
}
// let the value of current attribute be the pivot
pivot = item[attr];
if (!isNaN(pivot)) {
pivot = parseFloat(pivot);
}
// pick the predicate
// depending on the type of the attribute value
//var predicateName;
if (typeof pivot == 'number') {
//console.log('is number ' + pivot + ' ' + typeof pivot)
predicateName = '>=';
} else {
//console.log('is not number ' + pivot + ' ' + typeof pivot)
// there is no sense to compare non-numeric attributes
// so we will check only equality of such attributes
predicateName = '==';
}
attrPredPivot = attr + predicateName + pivot;
if (alreadyChecked[attrPredPivot]) {
// skip such pairs of 'attribute-predicate-pivot',
// which been already checked
continue;
}
alreadyChecked[attrPredPivot] = true;
predicate = predicates[predicateName];
// splitting training set by given 'attribute-predicate-value'
currSplit = split(trainingSet, attr, predicate, pivot);
////console.log(currSplit)
// calculating entropy of subsets
matchEntropy = entropy(currSplit.match, categoryAttr);
notMatchEntropy = entropy(currSplit.notMatch, categoryAttr);
////console.log(bestSplit.gain)
// calculating informational gain
newEntropy = 0;
newEntropy += matchEntropy * currSplit.match.length;
newEntropy += notMatchEntropy * currSplit.notMatch.length;
newEntropy /= trainingSet.length;
currGain = initialEntropy - newEntropy;
//console.log("CURRENT GAIN 2"+currGain)
if (currGain > bestSplit.gain) {
// remember pairs 'attribute-predicate-value'
// which provides informational gain
bestSplit = currSplit;
bestSplit.predicateName = predicateName;
bestSplit.predicate = predicate;
bestSplit.attribute = attr;
bestSplit.pivot = pivot;
bestSplit.gain = currGain;
}
}
}
}
console.log(bestSplit.gain);
if (!bestSplit.gain) {
let _category = mostFrequentValue(trainingSet, categoryAttr);
let _positiveCounter = 0;
let _hide = 0;
//console.log(trainingSet);
trainingSet.forEach(element => {
if (element[categoryAttr] === _category) _positiveCounter++;
});
let _negativeCounter = trainingSet.length - _positiveCounter;
_quality = _positiveCounter / trainingSet.length;
if (_quality !== 1) _hide = 25;
_quality = _quality * 100;
// can't find optimal split
return {
category: _category,
quality: _quality,
matchedCount: _positiveCounter,
notMatchedCount: _negativeCounter,
trainingSet2: trainingSet.map(x => x[categoryAttr]),
};
}
//console.log(window.list_obj)
// building subtrees
builder.maxTreeDepth = maxTreeDepth - 1;
builder.trainingSet = bestSplit.match;
var matchSubTree = buildDecisionTreeC45(builder);
builder.trainingSet = bestSplit.notMatch;
var notMatchSubTree = buildDecisionTreeC45(builder);
return {
attr2: bestSplit.attribute,
predicateName: bestSplit.predicateName,
pivot: bestSplit.pivot,
match: matchSubTree,
notMatch: notMatchSubTree,
matchedCount: bestSplit.match.length,
notMatchedCount: bestSplit.notMatch.length,
nodeSet: bestSplit.match.concat(bestSplit.notMatch),
};
}
function countUniqueValues(items, attr) {
var counter = {};
// detecting different values of attribute
for (var i = items.length - 1; i >= 0; i--) {
// items[i][attr] - value of attribute
counter[items[i][attr]] = 0;
}
// counting number of occurrences of each of values
// of attribute
for (var j = items.length - 1; j >= 0; j--) {
counter[items[j][attr]] += 1;
}
return counter;
}
function mostFrequentValue(items, attr) {
// counting number of occurrences of each of values
// of attribute
var counter = countUniqueValues(items, attr);
var mostFrequentCount = 0;
var mostFrequentValue;
for (var value in counter) {
if (counter[value] > mostFrequentCount) {
mostFrequentCount = counter[value];
mostFrequentValue = value;
}
}
return mostFrequentValue;
}
/**
* Calculating entropy of array of objects
* by specific attribute.
*
* @param items - array of objects
*
* @param attr - variable with name of attribute,
* which embedded in each object
*/
function entropy(items, attr) {
// counting number of occurrences of each of values
// of attribute
var counter = countUniqueValues(items, attr);
var entropy = 0;
var p;
for (var i in counter) {
p = counter[i] / items.length;
entropy += -p * Math.log(p);
}
return entropy;
}
/**
* Splitting array of objects by value of specific attribute,
* using specific predicate and pivot.
*
* Items which matched by predicate will be copied to
* the new array called 'match', and the rest of the items
* will be copied to array with name 'notMatch'
*
* @param items - array of objects
*
* @param attr - variable with name of attribute,
* which embedded in each object
*
* @param predicate - function(x, y)
* which returns 'true' or 'false'
*
* @param pivot - used as the second argument when
* calling predicate function:
* e.g. predicate(item[attr], pivot)
*/
function split(items, attr, predicate, pivot) {
var match = [];
var notMatch = [];
var item, attrValue;
for (var i = items.length - 1; i >= 0; i--) {
item = items[i];
attrValue = item[attr];
if (predicate(attrValue, pivot)) {
match.push(item);
} else {
notMatch.push(item);
}
}
return {
match: match,
notMatch: notMatch,
};
}
var predicates = {
'==': function (a, b) {
return a === b;
},
'>=': function (a, b) {
return a >= b;
},
};
/** @type {Worker} */
// @ts-ignore
const context = self; //eslint-disable-line
context.onmessage = function (event) {
console.log('received message', event);
const {
data: { _builder, isChanged = false, changedAttribute1 = null, changedAttribute2 = null },
} = event;
const result = buildDecisionTreeC45(_builder, isChanged, changedAttribute1, changedAttribute2);
console.log(result);
context.postMessage(result);
};

View File

@@ -0,0 +1,317 @@
// @ts-check
/**
* @typedef {Object} DecisionTreeBuilder
* @property {Array<Object>} trainingSet
* @property {Array<string>} allAttributes
* @property {Array<string>} allClasses
* @property {number} minItemsCount
* @property {string} categoryAttr
* @property {number} entropyThrehold
* @property {number} maxTreeDepth
* @property {Array<string>} ignoredAttributes
*/
/**
* @param {DecisionTreeBuilder} _builder
* @param {boolean} isChanged
*/
//TSP
function buildDecisionTreeTSP(
_builder,
isChanged = false,
changedAttribute1 = null,
changedAttribute2 = null
) {
//debugger;
const builder = { ..._builder };
const {
trainingSet,
minItemsCount,
categoryAttr,
entropyThrehold,
maxTreeDepth,
ignoredAttributes,
} = builder;
//console.log("########## NOWY WEZEL ########", trainingSet.length);
/** @type {string | number} */
var _quality = 0;
if (maxTreeDepth === 0 || trainingSet.length <= minItemsCount) {
console.log('LISC BO MAX TREE DEPTH', maxTreeDepth, 'LISC ILOSC', trainingSet.length);
//gger;
let _category = mostFrequentValue(trainingSet, categoryAttr);
let _positiveCounter = 0;
//console.log("KATEGORIA JAKO:", _category);
trainingSet.forEach(element => {
if (element[categoryAttr] == _category) _positiveCounter++;
});
let _negativeCounter = trainingSet.length - _positiveCounter;
_quality = _positiveCounter / trainingSet.length;
_quality = _quality * 100;
_quality = _quality.toFixed(2);
//ugger;
return {
category: _category,
quality: _quality,
matchedCount: _positiveCounter,
notMatchedCount: _negativeCounter,
trainingSet2: trainingSet.map(x => x[categoryAttr]),
};
}
var attributes = builder.allAttributes.filter(function (el) {
return ![...ignoredAttributes, categoryAttr].includes(el);
});
//console.log(builder.minItemsCount, builder.trainingSet.length);
// tu juz musi byc przekazana cm wyzerowana
var podzial = [];
//console.log(attributes);
var right = 0,
left = 0;
var maxDif = 100;
/** @type {string | number} */ var attribute1 = -1;
/** @type {string | number} */ var attribute2 = -1;
var directrion = '<';
var leftList = [],
rightList = [],
classMatrix = [
new Array(builder.allClasses.length).fill(0),
new Array(builder.allClasses.length).fill(0),
],
match = [],
notMatch = [];
//#########################
//# tu gdy zmieniamy #
//#########################
if (isChanged) {
right = left = 0;
leftList = [];
rightList = [];
classMatrix = [
new Array(builder.allClasses.length).fill(0),
new Array(builder.allClasses.length).fill(0),
];
for (let index = 0; index < trainingSet.length; index++) {
const element = trainingSet[index];
if (element[changedAttribute1] < element[changedAttribute2]) {
left++;
leftList.push(element);
classMatrix[0][builder.allClasses.indexOf(element[categoryAttr])]++;
} else {
right++;
rightList.push(element);
classMatrix[1][builder.allClasses.indexOf(element[categoryAttr])]++;
}
}
//console.log(classMatrix);
var probR = 0,
probL = 0,
rankL = 0,
rankR = 0;
for (let k = 0; k < builder.allClasses.length; k++) {
probL = left === 0 ? 0 : classMatrix[0][k] / left;
probR = right === 0 ? 0 : classMatrix[1][k] / right;
rankL += probL * probL;
rankR += probR * probR;
}
//console.log("Rank Lewy",rankL,"Rank Prawy",rankR);
var currentDif = (right / trainingSet.length) * (1 - rankR) + (left / trainingSet.length) * (1 - rankL);
if (currentDif < maxDif) {
//console.log("------Zapisanie maxDif-------");
//console.log(attr1,attr2);
//console.log("R/L ", right + ":" + left);
//console.log("cur/mD",currentDif + ":" + maxDif);
maxDif = currentDif;
attribute1 = changedAttribute1;
attribute2 = changedAttribute2;
match = leftList;
notMatch = rightList;
podzial = classMatrix;
//console.log("-----------------------------");
}
} else {
for (let i = 0; i < attributes.length; i++) {
let attr1 = attributes[i];
for (let j = 0; j < attributes.length; j++) {
let attr2 = attributes[j];
if (attr1 !== attr2) {
right = left = 0;
leftList = [];
rightList = [];
classMatrix = [
new Array(builder.allClasses.length).fill(0),
new Array(builder.allClasses.length).fill(0),
];
for (let index = 0; index < trainingSet.length; index++) {
const element = trainingSet[index];
if (element[attr1] < element[attr2]) {
left++;
leftList.push(element);
classMatrix[0][builder.allClasses.indexOf(element[categoryAttr])]++;
} else {
right++;
rightList.push(element);
classMatrix[1][builder.allClasses.indexOf(element[categoryAttr])]++;
}
}
//console.log(classMatrix);
var probR = 0,
probL = 0,
rankL = 0,
rankR = 0;
for (let k = 0; k < builder.allClasses.length; k++) {
probL = left === 0 ? 0 : classMatrix[0][k] / left;
probR = right === 0 ? 0 : classMatrix[1][k] / right;
rankL += probL * probL;
rankR += probR * probR;
}
//console.log("Rank Lewy",rankL,"Rank Prawy",rankR);
var currentDif =
(right / trainingSet.length) * (1 - rankR) + (left / trainingSet.length) * (1 - rankL);
if (currentDif < maxDif) {
//console.log("------Zapisanie maxDif-------");
//console.log(attr1,attr2);
//console.log("R/L ", right + ":" + left);
//console.log("cur/mD",currentDif + ":" + maxDif);
maxDif = currentDif;
attribute1 = attr1;
attribute2 = attr2;
match = leftList;
notMatch = rightList;
podzial = classMatrix;
//console.log("-----------------------------");
}
}
}
}
}
//console.log("PO WYLICZENIU NAJLEPSZEGO");
//console.log(attribute1, attribute2);
//console.log("L/R ", match.length + ":" + notMatch.length);
//console.log(podzial);
console.log('MaxDifference:', maxDif);
if (!maxDif) {
//console.log("LISC BO MAX DIF ZERO", trainingSet.length);
let _category = mostFrequentValue(trainingSet, categoryAttr);
let _positiveCounter = 0;
//console.log("KATEGORIA JAKO:", _category);
trainingSet.forEach(element => {
if (element[categoryAttr] == _category) _positiveCounter++;
});
let _negativeCounter = trainingSet.length - _positiveCounter;
_quality = _positiveCounter / trainingSet.length;
_quality = _quality * 100;
_quality = _quality.toFixed(2);
return {
category: _category,
quality: _quality,
matchedCount: _positiveCounter,
notMatchedCount: _negativeCounter,
trainingSet2: trainingSet.map(x => x[categoryAttr]),
};
}
// sprawdzic
// wssytskies stringi do ignored
if (match.length === 0 || notMatch.length === 0) {
console.log('LISC BO JEDNA ZE STRON MA 0');
let _category = mostFrequentValue(trainingSet, categoryAttr);
let _positiveCounter = 0;
//console.log(_category);
trainingSet.forEach(element => {
if (element[categoryAttr] == _category) _positiveCounter++;
});
let _negativeCounter = trainingSet.length - _positiveCounter;
_quality = _positiveCounter / trainingSet.length;
_quality = _quality * 100;
_quality = _quality.toFixed(2);
// restriction by maximal depth of tree
// or size of training set is to small
// so we have to terminate process of building tree
return {
category: _category,
quality: _quality,
matchedCount: _positiveCounter,
notMatchedCount: _negativeCounter,
trainingSet2: trainingSet.map(x => x[categoryAttr]),
};
}
builder.maxTreeDepth = maxTreeDepth - 1;
builder.trainingSet = match;
var matchSubTree = buildDecisionTreeTSP(builder);
builder.trainingSet = notMatch;
var notMatchSubTree = buildDecisionTreeTSP(builder);
console.log('TUTAJ');
return {
attr2: attribute2,
pivot: attribute1,
predicateName: directrion,
match: matchSubTree,
notMatch: notMatchSubTree, //{category: ...}
matchedCount: match.length,
notMatchedCount: notMatch.length,
nodeSet: match.concat(notMatch),
};
//console.log(attributes);
}
function countUniqueValues(items, attr) {
var counter = {};
// detecting different values of attribute
for (var i = items.length - 1; i >= 0; i--) {
// items[i][attr] - value of attribute
counter[items[i][attr]] = 0;
}
// counting number of occurrences of each of values
// of attribute
for (var j = items.length - 1; j >= 0; j--) {
counter[items[j][attr]] += 1;
}
return counter;
}
function mostFrequentValue(items, attr) {
// counting number of occurrences of each of values
// of attribute
var counter = countUniqueValues(items, attr);
var mostFrequentCount = 0;
var mostFrequentValue;
for (var value in counter) {
if (counter[value] > mostFrequentCount) {
mostFrequentCount = counter[value];
mostFrequentValue = value;
}
}
return mostFrequentValue;
}
/** @type {Worker} */
// @ts-ignore
const context = self; //eslint-disable-line
context.onmessage = function (event) {
console.log('received message', event);
const {
data: { _builder, isChanged = false, changedAttribute1 = null, changedAttribute2 = null },
} = event;
const result = buildDecisionTreeTSP(_builder, isChanged, changedAttribute1, changedAttribute2);
context.postMessage(result);
};

View File

@@ -21,7 +21,8 @@ export function buildDecisionTreeTSPW(
_builder,
isChanged = false,
changedAttribute1 = null,
changedAttribute2 = null
changedAttribute2 = null,
weight = 1
) {
//debugger;
const builder = { ..._builder };
@@ -60,7 +61,6 @@ export function buildDecisionTreeTSPW(
var right = 0,
left = 0,
weight = 0,
sum1 = 0,
sum2 = 0,
L_weight = 0;
@@ -262,7 +262,7 @@ export function buildDecisionTreeTSPW(
matchedCount: match.length,
notMatchedCount: notMatch.length,
nodeSet: match.concat(notMatch),
weight: weight,
weight: L_weight,
};
//console.log(attributes);
}
@@ -302,3 +302,15 @@ function mostFrequentValue(items, attr) {
return mostFrequentValue;
}
/** @type {Worker} */
// @ts-ignore
const context = self; //eslint-disable-line
context.onmessage = function (event) {
console.log('received message', event);
const {
data: { _builder, isChanged = false, changedAttribute1 = null, changedAttribute2 = null, weight = 1 },
} = event;
const result = buildDecisionTreeTSPW(_builder, isChanged, changedAttribute1, changedAttribute2, weight);
context.postMessage(result);
};

View File

@@ -3,6 +3,8 @@ import '../css/main.scss';
import Tree from './Tree';
import Navigation from './Navigation';
import { builder as _builder_ } from '../services/Playground';
import { AttributesProvider } from '../contexts/AttributesContext';
import { BuilderConfigProvider } from '../contexts/BuilderConfigContext';
function Main() {
const [builder, setBuilder] = useState({});
@@ -19,12 +21,16 @@ function Main() {
}
return (
<div id="main" className="main">
<Navigation onPrepareConfig={StartDrawing} />
{/* <Button onClick={start}>Drzewo</Button> */}
<br></br>
<div>{isReady ? <Tree options={builder} /> : <div>czekam na ciebie</div>}</div>
</div>
<BuilderConfigProvider>
<AttributesProvider>
<div id="main" className="main">
<Navigation onPrepareConfig={StartDrawing} />
{/* <Button onClick={start}>Drzewo</Button> */}
<br></br>
<div>{isReady ? <Tree options={builder} /> : <div>czekam na ciebie</div>}</div>
</div>
</AttributesProvider>
</BuilderConfigProvider>
);
}

View File

@@ -26,14 +26,18 @@ import Builder from './Builder';
import FormControlNumberInput from './FormControlNumberInput';
import DrawerRoll from './DrawerRoll';
import { useLoadingContext } from '../../contexts/LoadingContext';
import { useAttributesContext } from '../../contexts/AttributesContext';
import { useBuilderConfigContext } from '../../contexts/BuilderConfigContext';
/**
* @typedef {import('../../utils/decision-tree.js').DecisionTreeBuilder} DecisionTreeBuilder
*/
function Navigation({ onPrepareConfig }) {
const { setBuilderConfig } = useBuilderConfigContext();
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
const [dataSet, setDataSet] = useState(null);
const [algorithm, setAlgorithm] = useState('c45');
const [decisionAttribute, setDecisionAttribute] = useState(null);
const [ignoredAttributes, setIgnoredAttributes] = useState([]);
const [minItems, setMinItems] = useState(4);
@@ -42,11 +46,7 @@ function Navigation({ onPrepareConfig }) {
const [allAttrs, setAllAttributes] = useState([]);
const [allClazz, setAllClasses] = useState([]);
const [config, setConfig] = useState({});
const [options, setOptions] = useState([
{ value: 'hamburger', name: 'Hamburger' },
{ value: 'fries', name: 'Fries' },
{ value: 'milkshake', name: 'Milkshake' },
]);
const { attributes: options, onAttributesChange } = useAttributesContext();
const { isLoading } = useLoadingContext();
function handleSelectDecision(value) {
@@ -76,12 +76,8 @@ function Navigation({ onPrepareConfig }) {
*/
function handleGetAllAttributes({ allAttributes, data }) {
console.log(allAttributes, data);
let array = [];
allAttributes.forEach(element => {
array.push({ value: element, name: element });
});
setAllAttributes(allAttributes);
setOptions(array);
onAttributesChange(allAttributes);
setDataSet(data);
}
/**
@@ -92,17 +88,20 @@ function Navigation({ onPrepareConfig }) {
allAttributes: allAttrs,
allClasses: allClazz || [],
categoryAttr: decisionAttribute,
ignoredAttributes: ignoredAttributes,
entropyThrehold: entropy,
maxTreeDepth: maxDepth,
minItemsCount: minItems,
trainingSet: dataSet || [],
ignoredAttributes,
algorithm,
};
}
function handleDrawTree() {
setConfig(prepareConfig());
onPrepareConfig(prepareConfig());
const config = prepareConfig();
setConfig(config);
onPrepareConfig(config);
setBuilderConfig(config);
}
return (
@@ -132,6 +131,22 @@ function Navigation({ onPrepareConfig }) {
<FileReader onChange={handleGetAllAttributes} isHeaders={false} />{' '}
</FormControl>
</Box>
<Box w={'100%'}>
<FormControl id="algorithm" width="auto" zIndex={2}>
<FormLabel fontSize={['md', 'md', 'xs', 'sm', 'md']}>Algorithm</FormLabel>
<SearchBar
placeholder="Select algorithm"
onChange={setAlgorithm}
value={algorithm}
options={['c45', 'tsp', 'tspw'].map(v => ({ value: v, name: v.toUpperCase() }))}
multiple={false}
closeOnSelect={true}
/>
{/* <FormHelperText width={size}>
<Builder size={size} builder={prepareConfig()} />
</FormHelperText> */}
</FormControl>
</Box>
<Box w={'100%'}>
<FormControl id="decisionAttr" width="auto" zIndex={2}>
<FormLabel fontSize={['md', 'md', 'xs', 'sm', 'md']}>Decision attribute</FormLabel>

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { Input, Stack } from '@chakra-ui/react';
import { ChevronLeftIcon } from '@chakra-ui/icons';
import { Search as SearchBar } from '../../SearchBar';
import './alg-style.scss';
import { useAttributesContext } from '../../../contexts/AttributesContext';
export default function TabC45({ attribute, value, changeValues }) {
const { attributes: options } = useAttributesContext();
const onAttributeChange = value => changeValues({ attribute: value });
const onPivotChange = event => changeValues({ pivot: +event.target.value });
return (
<Stack spacing={3} direction="row">
{/* <div className="alg-search"> */}
<SearchBar
value={attribute}
onChange={onAttributeChange}
options={options}
multiple={false}
closeOnSelect={true}
/>
{/* </div> */}
<ChevronLeftIcon w={10} h={10} />
<Input value={value} size="md" name="c45-value" onChange={onPivotChange} />
</Stack>
);
}

View File

@@ -0,0 +1,35 @@
import React from 'react';
import { Input, Stack } from '@chakra-ui/react';
import { ChevronLeftIcon } from '@chakra-ui/icons';
import { Search as SearchBar } from '../../SearchBar';
import './alg-style.scss';
import { useAttributesContext } from '../../../contexts/AttributesContext';
export default function TabTSP({ attribute, value, changeValues }) {
const { attributes: options } = useAttributesContext();
const onAttributeChange = value => changeValues({ attribute: value });
const onPivotChange = value => changeValues({ pivot: value });
return (
<Stack spacing={3} direction="row">
{/* <div className="alg-search"> */}
<SearchBar
value={attribute}
onChange={onAttributeChange}
options={options}
multiple={false}
closeOnSelect={true}
/>
{/* </div> */}
<ChevronLeftIcon w={10} h={10} />
<SearchBar
value={value}
onChange={onPivotChange}
options={options}
multiple={false}
closeOnSelect={true}
/>
</Stack>
);
}

View File

@@ -0,0 +1,40 @@
import React from 'react';
import { Icon, Input, Stack } from '@chakra-ui/react';
import { ChevronLeftIcon } from '@chakra-ui/icons';
import { Search as SearchBar } from '../../SearchBar';
import './alg-style.scss';
import { useAttributesContext } from '../../../contexts/AttributesContext';
export default function TabTSPW({ attribute, value, weight, changeValues }) {
const { attributes: options } = useAttributesContext();
const onAttributeChange = value => changeValues({ attribute: value });
const onWeightChange = event => changeValues({ weight: +event.target.value });
const onPivotChange = value => changeValues({ pivot: value });
return (
<Stack spacing={3} direction="row">
{/* <div className="alg-search"> */}
<SearchBar
value={attribute}
onChange={onAttributeChange}
options={options}
multiple={false}
closeOnSelect={true}
/>
{/* </div> */}
<ChevronLeftIcon w={10} h={10} />
<Input value={weight} size="sm" name="c45-value" onChange={onWeightChange} />
<Icon viewBox="0 0 200 200" color="black.500">
<path fill="currentColor" d="M 100, 100 m -75, 0 a 75,75 0 1,0 150,0 a 75,75 0 1,0 -150,0" />
</Icon>
<SearchBar
value={value}
onChange={onPivotChange}
options={options}
multiple={false}
closeOnSelect={true}
/>
</Stack>
);
}

View File

@@ -0,0 +1,3 @@
.alg-search {
width: 110%;
}

View File

@@ -1,26 +1,33 @@
import { Button, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import TabC45 from './TabC45';
import TabTSP from './TabTSP';
import TabTSPW from './TabTSPW';
function Configurator({ onChange }) {
const algoritms = ['C45', 'TSP', 'TSPW'];
const algorithms = ['C45', 'TSP', 'TSPW'];
function Configurator({ onChange, attribute, pivot, weight }) {
const [tabIndex, setTabIndex] = useState(0);
const [state, setState] = useState({
param1: 'a',
param2: 'b',
param3: 'c',
algorithm: algoritms[tabIndex],
attribute,
pivot,
weight,
algorithm: algorithms[tabIndex],
});
useEffect(() => {
setState({ ...state, algorithm: algorithms[tabIndex] });
}, [tabIndex]); // eslint-disable-line
function handleConfirmChange() {
let newState = state;
newState = { ...state, algorithm: algoritms[tabIndex] };
setState(newState);
onChange(newState);
onChange({ ...state });
}
const handleValuesChange = values => {
setState({ ...state, ...values });
};
return (
<div>
<Tabs isFitted variant="line" colorScheme="blackAplha" onChange={index => setTabIndex(index)}>
<Tabs isFitted variant="line" colorScheme="blackAlpha" value={tabIndex} onChange={setTabIndex}>
<TabList mb="1em">
<Tab>C 4.5</Tab>
<Tab>TSP</Tab>
@@ -28,13 +35,18 @@ function Configurator({ onChange }) {
</TabList>
<TabPanels>
<TabPanel>
<p>one!</p>
<TabC45 attribute={state.attribute} value={state.pivot} changeValues={handleValuesChange} />
</TabPanel>
<TabPanel>
<p>two!</p>
<TabTSP attribute={state.attribute} value={state.pivot} changeValues={handleValuesChange} />
</TabPanel>
<TabPanel>
<p>three!</p>
<TabTSPW
attribute={state.attribute}
value={state.pivot}
weight={state.weight}
changeValues={handleValuesChange}
/>
</TabPanel>
</TabPanels>
</Tabs>

View File

@@ -44,6 +44,7 @@ function Joint({ children, attr2, predicateName, pivot, match, notMatch, onChang
attr2={attr2}
predicateName={predicateName}
pivot={pivot}
onChange={onChange}
onClose={onClose}
onOpen={onOpen}
/>

View File

@@ -11,11 +11,11 @@ import {
} from '@chakra-ui/react';
import Configurator from './Configurator';
function ModalPopup({ attr2, predicateName, pivot, isOpen, nodeSet, onOpen, onClose }) {
function handleChange(value) {
console.log(value);
function ModalPopup({ attr2, predicateName, pivot, isOpen, nodeSet, onOpen, onChange, onClose }) {
const handleOnChange = value => {
onClose();
}
onChange(value);
};
return (
<Modal onClose={onClose} size="lg" isOpen={isOpen}>
@@ -26,13 +26,13 @@ function ModalPopup({ attr2, predicateName, pivot, isOpen, nodeSet, onOpen, onCl
</ModalHeader>
<ModalCloseButton />
<ModalBody>
Text ( kolejny komponent z konfiguratorem){' '}
{/* Text ( kolejny komponent z konfiguratorem){' '}
{nodeSet.map((x, index) => (
<div key={index}>
{x.attr1001} {x[attr2]}-{x[pivot]} {x[attr2] < x[pivot] ? 'Match' : 'NotMatch'}
</div>
))}
<Configurator onChange={handleChange} />
))} */}
<Configurator onChange={handleOnChange} attribute={attr2} pivot={pivot} />
</ModalBody>
<ModalFooter>
<Button onClick={onClose}>Close</Button>

View File

@@ -1,4 +1,7 @@
import { Spinner } from '@chakra-ui/react';
import React, { useEffect, useMemo, useState } from 'react';
import { useBuilderConfigContext } from '../../contexts/BuilderConfigContext';
import { executeAlgorithm } from '../../utils/algorithm-executor';
//import { addNode } from "./utils";
import Joint from './Joint';
@@ -6,7 +9,9 @@ import Leaf from './Leaf';
const Node = props => {
//console.log(props.node);
const { builderConfig } = useBuilderConfigContext();
const [highlighted, setHighlighted] = useState(false);
const [loading, setLoading] = useState(false);
const [node, setNode] = useState(props.node || {});
useEffect(() => setNode(props.node || {}), [props.node, setNode]);
const {
@@ -33,21 +38,57 @@ const Node = props => {
setTimeout(() => setHighlighted(false), 500);
};
const onChange = useMemo(() => {
return node => {
console.log(node);
// const index = match.findIndex(c => c.id === node.id);
// console.log(index);
// console.log(match);
// match.splice(index, 1, node);
// props.node.match = [...match];
const onChange = options => {
if (!options) {
return;
}
console.log('builderConfig', builderConfig);
const builderModel = {
...builderConfig,
trainingSet: node.nodeSet,
algorithm: options.algorithm.toLowerCase(),
};
}, [match, props.node]);
const changeOptions = {
isChanged: true,
changedAttribute1: options.attr2 || node.attr2,
changedAttribute2: options.pivot || node.pivot,
weight: typeof options.weight === 'number' ? options.weight : node.weight,
};
console.log('partial builder model', builderModel, changeOptions);
setLoading(true);
executeAlgorithm(builderModel, changeOptions)
.then(value => {
setNode(value);
props.requestChildChange(value);
})
.catch(e => console.error(e))
.finally(() => {
setLoading(false);
});
// const index = match.findIndex(c => c.id === node.id);
// console.log(index);
// console.log(match);
// match.splice(index, 1, node);
// props.node.match = [...match];
};
useEffect(() => {
console.log(category, category ? 'Leaf' : 'Joint');
}, [category]);
const requestChildChangeIfMatchIs = match => newNode => {
const targetNode = {
...node,
[match ? 'match' : 'notMatch']: newNode,
};
setNode(targetNode);
props.requestChildChange(targetNode);
};
if (loading) {
return <Spinner size="xl" />;
}
return (
<div className={`node ${highlighted ? 'highlight' : ''}`} onClick={onNodeClicked}>
{category ? (
@@ -67,8 +108,8 @@ const Node = props => {
onChange={onChange}
nodeSet={nodeSet}
>
<Node node={match} onChange={onChange} />
<Node node={notMatch} onChange={onChange} />
<Node node={match} onChange={onChange} requestChildChange={requestChildChangeIfMatchIs(true)} />
<Node node={notMatch} onChange={onChange} requestChildChange={requestChildChangeIfMatchIs(false)} />
</Joint>
)}
</div>

View File

@@ -12,5 +12,6 @@ export const Search = props => (
printOptions="on-focus"
closeOnSelect={props.closeOnSelect}
placeholder={props.placeholder}
value={props.value}
/>
);

View File

@@ -4,12 +4,13 @@ import { Box, Spinner } from '@chakra-ui/react';
// import { dt } from '../services/TSP';
import Node from './Node';
import { useLoadingContext } from '../contexts/LoadingContext';
import { executeAlgorithm } from '../utils/algorithm-executor';
/**
* @typedef {import('../utils/decision-tree.js').DecisionTreeBuilder} DecisionTreeBuilder
*/
const logTree = root => console.log(root);
const logTree = root => console.log('ROOT', root);
/**
* @param {Object} props
@@ -32,36 +33,45 @@ const Tree = ({ options }) => {
setRoot(null);
setIsLoading(true);
let terminated = false;
const worker = new Worker('decision-tree.js');
worker.onmessage = ({ data }) => {
console.log('got a result', data);
if (terminated) return;
setRoot(data);
worker.terminate();
terminated = true;
setIsLoading(false);
};
worker.postMessage({ _builder: { ...options } });
executeAlgorithm(options)
.then(value => {
if (terminated) {
return;
}
setRoot(value);
})
.catch(e => console.error(e))
.finally(() => {
if (!terminated) setIsLoading(false);
terminated = true;
});
return () => {
if (terminated) {
return;
}
worker.terminate();
terminated = true;
};
}, [options]);
}, [options, setIsLoading]);
useEffect(() => logTree(root), [root]);
const requestChildChange = newRoot => setRoot(newRoot);
return (
<div id="tree">
<h1>Tree</h1>
<p>
<button onClick={logTree}>Log tree</button>
<button onClick={() => logTree(root)}>Log tree</button>
</p>
{isLoading && <Spinner size="xl" />}
<h2>Nodes:</h2>
<Box>{!root ? <p>No tree to show</p> : <Node node={root} onChange={() => {}} />}</Box>
<Box>
{!root ? (
<p>No tree to show</p>
) : (
<Node node={root} onChange={() => {}} requestChildChange={requestChildChange} />
)}
</Box>
</div>
);
};

View File

@@ -0,0 +1,26 @@
import React, { useContext, useState } from 'react';
// @ts-ignore
const AttributesContext = React.createContext();
export const AttributesProvider = ({ children }) => {
const [attributes, setAttributes] = useState([
{ value: 'hamburger', name: 'Hamburger' },
{ value: 'fries', name: 'Fries' },
{ value: 'milkshake', name: 'Milkshake' },
]);
const onAttributesChange = input =>
setAttributes(input.map(element => ({ value: element, name: element })));
return (
<AttributesContext.Provider value={{ attributes, onAttributesChange }}>
{children}
</AttributesContext.Provider>
);
};
export const useAttributesContext = () => {
const { attributes, onAttributesChange } = useContext(AttributesContext);
return { attributes, onAttributesChange };
};

View File

@@ -0,0 +1,19 @@
import React, { useContext, useState } from 'react';
// @ts-ignore
const BuilderConfigContext = React.createContext();
export const BuilderConfigProvider = ({ children }) => {
const [builderConfig, setBuilderConfig] = useState(null);
return (
<BuilderConfigContext.Provider value={{ builderConfig, setBuilderConfig }}>
{children}
</BuilderConfigContext.Provider>
);
};
export const useBuilderConfigContext = () => {
const { builderConfig, setBuilderConfig } = useContext(BuilderConfigContext);
return { builderConfig, setBuilderConfig };
};

View File

@@ -7,7 +7,8 @@ $raisin-black: #1b1b1eff;
* Main wrapper
*/
.select-search {
width: auto;
width: 100%;
height: 40px;
position: relative;
font-family: 'Nunito Sans', sans-serif;
box-sizing: border-box;
@@ -25,6 +26,7 @@ $raisin-black: #1b1b1eff;
.select-search__value {
position: relative;
z-index: 1;
width: 100%;
}
.select-search__value::after {

View File

@@ -0,0 +1,19 @@
const workersMap = {
c45: 'c45',
tsp: 'tsp',
tspw: 'tsp-weight',
};
export function executeAlgorithm(options, changeOptions = {}) {
console.log(options);
return new Promise((resolve, reject) => {
const worker = new Worker(`algorithms/${workersMap[options.algorithm]}-tree.js`);
worker.onmessage = ({ data }) => {
console.log('got a result', data);
resolve(data);
worker.terminate();
};
worker.onerror = reject;
worker.postMessage({ _builder: { ...options }, ...changeOptions });
});
}

View File

View File