diff --git a/public/algorithms/abc.js b/public/algorithms/abc.js new file mode 100644 index 0000000..2cd8e80 --- /dev/null +++ b/public/algorithms/abc.js @@ -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) {} diff --git a/public/algorithms/c45-tree.js b/public/algorithms/c45-tree.js new file mode 100644 index 0000000..b06ad35 --- /dev/null +++ b/public/algorithms/c45-tree.js @@ -0,0 +1,413 @@ +// @ts-check + +/** + * @typedef {Object} DecisionTreeBuilder + * @property {Array} trainingSet + * @property {Array} allAttributes + * @property {Array} allClasses + * @property {number} minItemsCount + * @property {string} categoryAttr + * @property {number} entropyThrehold + * @property {number} maxTreeDepth + * @property {Array} 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); +}; diff --git a/public/algorithms/tsp-tree.js b/public/algorithms/tsp-tree.js new file mode 100644 index 0000000..b047393 --- /dev/null +++ b/public/algorithms/tsp-tree.js @@ -0,0 +1,317 @@ +// @ts-check + +/** + * @typedef {Object} DecisionTreeBuilder + * @property {Array} trainingSet + * @property {Array} allAttributes + * @property {Array} allClasses + * @property {number} minItemsCount + * @property {string} categoryAttr + * @property {number} entropyThrehold + * @property {number} maxTreeDepth + * @property {Array} 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); +}; diff --git a/src/utils/algorithms/tsp-weight-tree.js b/public/algorithms/tsp-weight-tree.js similarity index 94% rename from src/utils/algorithms/tsp-weight-tree.js rename to public/algorithms/tsp-weight-tree.js index bc1084a..728052c 100644 --- a/src/utils/algorithms/tsp-weight-tree.js +++ b/public/algorithms/tsp-weight-tree.js @@ -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); +}; diff --git a/src/components/Main.jsx b/src/components/Main.jsx index b48fc3d..97e2eb8 100644 --- a/src/components/Main.jsx +++ b/src/components/Main.jsx @@ -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 ( -
- - {/* */} -

-
{isReady ? :
czekam na ciebie
}
-
+ + +
+ + {/* */} +

+
{isReady ? :
czekam na ciebie
}
+
+
+
); } diff --git a/src/components/Navigation/index.jsx b/src/components/Navigation/index.jsx index fe62173..a0f2b64 100644 --- a/src/components/Navigation/index.jsx +++ b/src/components/Navigation/index.jsx @@ -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 }) { {' '} + + + Algorithm + ({ value: v, name: v.toUpperCase() }))} + multiple={false} + closeOnSelect={true} + /> + {/* + + */} + + Decision attribute diff --git a/src/components/Node/Configurator/TabC45.jsx b/src/components/Node/Configurator/TabC45.jsx index e69de29..8784f78 100644 --- a/src/components/Node/Configurator/TabC45.jsx +++ b/src/components/Node/Configurator/TabC45.jsx @@ -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 ( + + {/*
*/} + + {/*
*/} + + +
+ ); +} diff --git a/src/components/Node/Configurator/TabTSP.jsx b/src/components/Node/Configurator/TabTSP.jsx index e69de29..e1e6899 100644 --- a/src/components/Node/Configurator/TabTSP.jsx +++ b/src/components/Node/Configurator/TabTSP.jsx @@ -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 ( + + {/*
*/} + + {/*
*/} + + +
+ ); +} diff --git a/src/components/Node/Configurator/TabTSPW.jsx b/src/components/Node/Configurator/TabTSPW.jsx index e69de29..2e0b297 100644 --- a/src/components/Node/Configurator/TabTSPW.jsx +++ b/src/components/Node/Configurator/TabTSPW.jsx @@ -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 ( + + {/*
*/} + + {/*
*/} + + + + + + +
+ ); +} diff --git a/src/components/Node/Configurator/alg-style.scss b/src/components/Node/Configurator/alg-style.scss new file mode 100644 index 0000000..ea62872 --- /dev/null +++ b/src/components/Node/Configurator/alg-style.scss @@ -0,0 +1,3 @@ +.alg-search { + width: 110%; +} diff --git a/src/components/Node/Configurator/index.jsx b/src/components/Node/Configurator/index.jsx index 41cdc7e..093c5c5 100644 --- a/src/components/Node/Configurator/index.jsx +++ b/src/components/Node/Configurator/index.jsx @@ -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 (
- setTabIndex(index)}> + C 4.5 TSP @@ -28,13 +35,18 @@ function Configurator({ onChange }) { -

one!

+
-

two!

+
-

three!

+
diff --git a/src/components/Node/Joint.jsx b/src/components/Node/Joint.jsx index 4285b89..4961af2 100644 --- a/src/components/Node/Joint.jsx +++ b/src/components/Node/Joint.jsx @@ -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} /> diff --git a/src/components/Node/ModalPopup.jsx b/src/components/Node/ModalPopup.jsx index 634a550..9ebf914 100644 --- a/src/components/Node/ModalPopup.jsx +++ b/src/components/Node/ModalPopup.jsx @@ -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 ( @@ -26,13 +26,13 @@ function ModalPopup({ attr2, predicateName, pivot, isOpen, nodeSet, onOpen, onCl - Text ( kolejny komponent z konfiguratorem){' '} + {/* Text ( kolejny komponent z konfiguratorem){' '} {nodeSet.map((x, index) => (
{x.attr1001} {x[attr2]}-{x[pivot]} {x[attr2] < x[pivot] ? 'Match' : 'NotMatch'}
- ))} - + ))} */} +
diff --git a/src/components/Node/index.jsx b/src/components/Node/index.jsx index 3d2d35c..22346ff 100644 --- a/src/components/Node/index.jsx +++ b/src/components/Node/index.jsx @@ -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 ; + } + return (
{category ? ( @@ -67,8 +108,8 @@ const Node = props => { onChange={onChange} nodeSet={nodeSet} > - - + + )}
diff --git a/src/components/SearchBar.jsx b/src/components/SearchBar.jsx index db636ef..5472911 100644 --- a/src/components/SearchBar.jsx +++ b/src/components/SearchBar.jsx @@ -12,5 +12,6 @@ export const Search = props => ( printOptions="on-focus" closeOnSelect={props.closeOnSelect} placeholder={props.placeholder} + value={props.value} /> ); diff --git a/src/components/Tree.jsx b/src/components/Tree.jsx index 6815974..01ae8e2 100644 --- a/src/components/Tree.jsx +++ b/src/components/Tree.jsx @@ -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 (

Tree

- +

{isLoading && }

Nodes:

- {!root ?

No tree to show

: {}} />}
+ + {!root ? ( +

No tree to show

+ ) : ( + {}} requestChildChange={requestChildChange} /> + )} +
); }; diff --git a/src/contexts/AttributesContext.jsx b/src/contexts/AttributesContext.jsx new file mode 100644 index 0000000..10e8f8b --- /dev/null +++ b/src/contexts/AttributesContext.jsx @@ -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 ( + + {children} + + ); +}; + +export const useAttributesContext = () => { + const { attributes, onAttributesChange } = useContext(AttributesContext); + return { attributes, onAttributesChange }; +}; diff --git a/src/contexts/BuilderConfigContext.jsx b/src/contexts/BuilderConfigContext.jsx new file mode 100644 index 0000000..ba4901b --- /dev/null +++ b/src/contexts/BuilderConfigContext.jsx @@ -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 ( + + {children} + + ); +}; + +export const useBuilderConfigContext = () => { + const { builderConfig, setBuilderConfig } = useContext(BuilderConfigContext); + return { builderConfig, setBuilderConfig }; +}; diff --git a/src/css/searchBar.scss b/src/css/searchBar.scss index 50411d2..fecbc66 100644 --- a/src/css/searchBar.scss +++ b/src/css/searchBar.scss @@ -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 { diff --git a/src/utils/algorithm-executor.js b/src/utils/algorithm-executor.js new file mode 100644 index 0000000..332e0d1 --- /dev/null +++ b/src/utils/algorithm-executor.js @@ -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 }); + }); +} diff --git a/src/utils/algorithms/c45-tree.js b/src/utils/algorithms/c45-tree.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/utils/algorithms/tsp-tree.js b/src/utils/algorithms/tsp-tree.js deleted file mode 100644 index e69de29..0000000