|
@@ -270,7 +270,7 @@
|
|
|
<div class="row m-0 h-100">
|
|
|
<div class="{{$multiProView ? 'col-12' : 'col-6'}} tree-lhs-column border-right h-100 p-0">
|
|
|
<div class="d-flex flex-column h-100">
|
|
|
- <div class="d-flex align-items-center pl-2 height-35px border-bottom bg-light">
|
|
|
+ <div class="d-flex align-items-center pl-2 height-35px border-bottom bg-light tree-column-header">
|
|
|
<h6 class="font-weight-bold m-0 text-secondary">{{$statTree->name}}</h6>
|
|
|
<span class="text-danger d-none if-changed ml-2 text-sm">(modified *)</span>
|
|
|
@if($multiProView)
|
|
@@ -294,7 +294,7 @@
|
|
|
<a href="#" class="tree-collapse-all ml-2" title="Collapse All"><i class="fa fa-angle-double-up text-secondary"></i></a>
|
|
|
<div class="ml-auto mr-2 d-none if-changed">
|
|
|
<a href="#" class="btn btn-sm btn-primary text-white" id="btn-save-tree">Save</a>
|
|
|
- <a href="#" class="ml-2 btn btn-sm btn-default border text-dark" onclick="return fastReload()">Reset</a>
|
|
|
+ <a href="#" class="ml-1 btn btn-sm btn-default border text-dark bg-white" onclick="return fastReload()">Reset</a>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="flex-grow-1 overflow-overlay-on-hover">
|
|
@@ -302,12 +302,12 @@
|
|
|
<div class="stat-tree-view overflow-auto min-height-300px" id="stat-tree-edit-{{$statTree->id}}"></div>
|
|
|
@else
|
|
|
<div class="d-flex align-items-start">
|
|
|
- <div class="">
|
|
|
+ <div class="mv-tree">
|
|
|
<div class="multi-pro-view-stat-label">Stat</div>
|
|
|
<div class="stat-tree-view overflow-auto" id="stat-tree-edit-{{$statTree->id}}"></div>
|
|
|
</div>
|
|
|
- <div class="flex-grow-1">
|
|
|
- <table class="table table-sm multi-pro-stats-table">
|
|
|
+ <div class="overflow-auto mv-table">
|
|
|
+ <table class="table table-sm multi-pro-stats-table w-auto m-0">
|
|
|
<thead>
|
|
|
<tr>
|
|
|
<th {{$statTree->pro ? 'data-pro-uid="' . $statTree->pro->uid . '"' : ''}}>
|
|
@@ -318,27 +318,25 @@
|
|
|
<div class="pro-label" title="{{$multiPro->name_display}}">{{$multiPro->name_display}}</div>
|
|
|
</th>
|
|
|
@endforeach
|
|
|
- <th></th>
|
|
|
</tr>
|
|
|
</thead>
|
|
|
<tbody>
|
|
|
@foreach($linesFlat as $line)
|
|
|
- <tr data-line-uid="{{$line->uid}}">
|
|
|
+ <tr data-line-uid="{{$line->uid}}" {!! $line->parent ? 'data-parent-line-uid="' . $line->parent->uid . '"' : '' !!}>
|
|
|
<td class="p-0">
|
|
|
<div class="count-label">
|
|
|
<div class="count">{{$line->last_refresh_count}}</div>
|
|
|
- <div class="drop-percent">50% ⤵</div>
|
|
|
+ <div class="drop-percent"></div>
|
|
|
</div>
|
|
|
</td>
|
|
|
@foreach($multiPros as $multiPro)
|
|
|
<td class="p-0" data-pro-uid="{{$multiPro->uid}}">
|
|
|
<div class="count-label">
|
|
|
- <div class="count">0</div>
|
|
|
- <div class="drop-percent">0% ⤵</div>
|
|
|
+ <div class="count"></div>
|
|
|
+ <div class="drop-percent"></div>
|
|
|
</div>
|
|
|
</td>
|
|
|
@endforeach
|
|
|
- <td></td>
|
|
|
</tr>
|
|
|
@endforeach
|
|
|
</tbody>
|
|
@@ -533,14 +531,17 @@
|
|
|
let StatTree = {
|
|
|
el: $('#stat-tree-edit-{{$statTree->id}}'),
|
|
|
changed: false,
|
|
|
+ copiedColumns: null,
|
|
|
|
|
|
setDirty: function(_changed = true) {
|
|
|
this.changed = _changed;
|
|
|
if(_changed) {
|
|
|
$('.if-changed').removeClass('d-none');
|
|
|
+ $('.tree-column-header').addClass('has-changes');
|
|
|
}
|
|
|
else {
|
|
|
$('.if-changed').addClass('d-none');
|
|
|
+ $('.tree-column-header').removeClass('has-changes');
|
|
|
}
|
|
|
},
|
|
|
|
|
@@ -654,6 +655,41 @@
|
|
|
},
|
|
|
separator_after: true,
|
|
|
},
|
|
|
+ "copy_columns": {
|
|
|
+ "label": "<span class='text-sm'>Copy Columns</span>",
|
|
|
+ "_disabled": !(node.data && node.data.columns && node.data.columns.length),
|
|
|
+ "action": function (obj) {
|
|
|
+ let selected = StatTree.selectedNode();
|
|
|
+ if(selected) {
|
|
|
+ localStorage.stPasteBuffer_Columns = JSON.stringify(selected.data.columns);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+ "paste_columns": {
|
|
|
+ "label": "<span class='text-sm'>Paste Columns</span>",
|
|
|
+ "_disabled": !localStorage.stPasteBuffer_Columns,
|
|
|
+ "action": function (obj) {
|
|
|
+ let selected = StatTree.selectedNode();
|
|
|
+ if(selected) {
|
|
|
+ StatTree.setSelectedNodeColumns(JSON.parse(localStorage.stPasteBuffer_Columns));
|
|
|
+ StatTree.onSelected();
|
|
|
+ StatTree.setDirty();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+ "paste_columns_deep": {
|
|
|
+ "label": "<span class='text-sm'>Paste Columns Recursively</span>",
|
|
|
+ "_disabled": !localStorage.stPasteBuffer_Columns,
|
|
|
+ "action": function (obj) {
|
|
|
+ let selected = StatTree.selectedNode();
|
|
|
+ if(selected) {
|
|
|
+ StatTree.pasteColumnsDeep(selected, localStorage.stPasteBuffer_Columns);
|
|
|
+ StatTree.onSelected();
|
|
|
+ StatTree.setDirty();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ separator_after: true,
|
|
|
+ },
|
|
|
"remove": {
|
|
|
"label": "<span class='text-sm'>Remove</span>",
|
|
|
"action": function (obj) {
|
|
@@ -662,6 +698,16 @@
|
|
|
StatTree.el.jstree(true).delete_node(selected.id);
|
|
|
StatTree.setDirty();
|
|
|
}
|
|
|
+ },
|
|
|
+ separator_after: true,
|
|
|
+ },
|
|
|
+ "permutate_child_tree": {
|
|
|
+ "label": "<span class='text-sm'>Create Child Permutations</span>",
|
|
|
+ "action": function (obj) {
|
|
|
+ let selected = StatTree.selectedNode();
|
|
|
+ if(selected) {
|
|
|
+ StatTree.createChildTreePermutations(selected);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -683,6 +729,120 @@
|
|
|
}, 'json').then(hideMask);
|
|
|
},
|
|
|
|
|
|
+ createChildTreePermutations: function(_node) {
|
|
|
+
|
|
|
+ // permute: thanks: https://stackoverflow.com/a/37580979/921204
|
|
|
+ function _permute(permutation) {
|
|
|
+ var length = permutation.length,
|
|
|
+ result = [permutation.slice()],
|
|
|
+ c = new Array(length).fill(0),
|
|
|
+ i = 1, k, p;
|
|
|
+
|
|
|
+ while (i < length) {
|
|
|
+ if (c[i] < i) {
|
|
|
+ k = i % 2 && c[i];
|
|
|
+ p = permutation[i];
|
|
|
+ permutation[i] = permutation[k];
|
|
|
+ permutation[k] = p;
|
|
|
+ ++c[i];
|
|
|
+ i = 1;
|
|
|
+ result.push(permutation.slice());
|
|
|
+ } else {
|
|
|
+ c[i] = 0;
|
|
|
+ ++i;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ensure 1 child for selected node
|
|
|
+ if(_node.children.length !== 1) {
|
|
|
+ toastr.error('Line must have exactly one child!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ensure all nodes down the node in the tree have only one child (if any)
|
|
|
+ function _hasSingleChild(_id) {
|
|
|
+ let node = StatTree.el.jstree(true).get_node(_id);
|
|
|
+ if(node.children.length === 0) return true; // no children
|
|
|
+ if(node.children.length > 1) return false; // more than 1 child, stop already!
|
|
|
+ return _hasSingleChild(node.children[0]);
|
|
|
+ }
|
|
|
+ if(!_hasSingleChild(_node.id)) {
|
|
|
+ toastr.error('Single linear child tree structure needed under selected node!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // get an array of line "data" of the child tree
|
|
|
+ function _getData(_id, _dataArray, _displayLabelArray) {
|
|
|
+ let node = StatTree.el.jstree(true).get_node(_id);
|
|
|
+ _dataArray.push(node.data);
|
|
|
+ _displayLabelArray.push(node.data.displayLabel);
|
|
|
+ if(node.children.length) {
|
|
|
+ _getData(node.children[0], _dataArray, _displayLabelArray);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let dataArray = [], displayLabelArray = [];
|
|
|
+ _getData(_node.children[0], dataArray, displayLabelArray);
|
|
|
+ let dataMap = {};
|
|
|
+ for (let i = 0; i < dataArray.length; i++) {
|
|
|
+ dataMap[dataArray[i].displayLabel] = JSON.parse(JSON.stringify(dataArray[i]));
|
|
|
+ delete dataMap[dataArray[i].displayLabel].dropPercent;
|
|
|
+ delete dataMap[dataArray[i].displayLabel].id;
|
|
|
+ delete dataMap[dataArray[i].displayLabel].lastRefreshCount;
|
|
|
+ delete dataMap[dataArray[i].displayLabel].treeOrderPositionIndex;
|
|
|
+ delete dataMap[dataArray[i].displayLabel].uid;
|
|
|
+ }
|
|
|
+
|
|
|
+ // get all permutations
|
|
|
+ let permutations = _permute(displayLabelArray);
|
|
|
+
|
|
|
+ // create child tree for each permutation
|
|
|
+ function _childTreeFromArray(_array, _index = 0) {
|
|
|
+ let children = [];
|
|
|
+ if(_index < _array.length - 1) {
|
|
|
+ children.push(_childTreeFromArray(_array, _index + 1));
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ text: '<span class="stat-tree-anchor">' + _array[_index] + '</span><span class="ml-2 text-secondary line-count-label">(…)</span>',
|
|
|
+ state: {
|
|
|
+ opened: true,
|
|
|
+ disabled: false,
|
|
|
+ selected: false,
|
|
|
+ },
|
|
|
+ children: children,
|
|
|
+ data: {
|
|
|
+ type: "stat_tree_line",
|
|
|
+ displayLabel: _array[_index],
|
|
|
+ columns: dataMap[_array[_index]].columns,
|
|
|
+ clause: dataMap[_array[_index]].clause
|
|
|
+ },
|
|
|
+ a_attr: {
|
|
|
+ title: dataMap[_array[_index]].clause.clause_text
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // clear current children
|
|
|
+ this.el.jstree(true).delete_node(_node.children[0]);
|
|
|
+
|
|
|
+ // create new child trees
|
|
|
+ for (let i = 0; i < permutations.length; i++) {
|
|
|
+ let subTree = _childTreeFromArray(permutations[i]);
|
|
|
+ this.el.jstree(true).create_node(_node.id, JSON.parse(JSON.stringify(subTree)));
|
|
|
+ }
|
|
|
+
|
|
|
+ this.setDirty();
|
|
|
+
|
|
|
+ },
|
|
|
+
|
|
|
+ pasteColumnsDeep: function(_node, _strColumns) {
|
|
|
+ _node.data.columns = JSON.parse(_strColumns);
|
|
|
+ for (let i = 0; i < _node.children.length; i++) {
|
|
|
+ this.pasteColumnsDeep(this.el.jstree(true).get_node(_node.children[i]), _strColumns);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
dropVisualize: function() {
|
|
|
console.log('redrawn')
|
|
|
function calculateDropPercent(node, parent = null) {
|
|
@@ -1137,6 +1297,34 @@
|
|
|
for(let x in _data) {
|
|
|
$('.multi-pro-stats-table tr[data-line-uid="' + x + '"] td[data-pro-uid="' + $(this).attr('data-pro-uid') + '"] .count').text(_data[x]);
|
|
|
}
|
|
|
+
|
|
|
+ // calculate drop %
|
|
|
+ $('.multi-pro-stats-table tr[data-line-uid][data-parent-line-uid]').each(function() {
|
|
|
+ for (let i = 0; i < $(this).find('>td').length; i++) {
|
|
|
+ let parentCount = $('.multi-pro-stats-table tr[data-line-uid="' + $(this).attr('data-parent-line-uid') + '"]>td:eq(' + i + ') .count').first().text(),
|
|
|
+ myCount = $(this).find('>td:eq(' + i + ') .count').first().text();
|
|
|
+ if(parentCount && myCount && !Number.isNaN(+parentCount) && !Number.isNaN(+myCount) && +parentCount > 0) {
|
|
|
+ parentCount = +parentCount;
|
|
|
+ myCount = +myCount;
|
|
|
+ let element = $(this).find('>td:eq(' + i + ') .drop-percent').first(), cssClass = '';
|
|
|
+ let dropPercent = ((parentCount - myCount) / parentCount) * 100;
|
|
|
+ if(dropPercent > 75) {
|
|
|
+ cssClass = 'mv-drop-76-100';
|
|
|
+ }
|
|
|
+ else if(dropPercent > 50 && dropPercent <= 75) {
|
|
|
+ cssClass = 'mv-drop-51-75';
|
|
|
+ }
|
|
|
+ else if(dropPercent > 25 && dropPercent <= 50) {
|
|
|
+ cssClass = 'mv-drop-26-50';
|
|
|
+ }
|
|
|
+ else if(dropPercent > 0 && dropPercent <= 25) {
|
|
|
+ cssClass = 'mv-drop-0-25';
|
|
|
+ }
|
|
|
+ element.removeClass().addClass('drop-percent').addClass(cssClass).text(dropPercent.toFixed(1) + '% ⤵');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
}, 'json');
|
|
|
});
|
|
|
@endif
|
|
@@ -1152,6 +1340,8 @@
|
|
|
initVSplitter('stat-tree-edit-1', $('.clauses-column'), $('.tree-column'));
|
|
|
@if(!$multiProView)
|
|
|
initVSplitter('stat-tree-edit-2', $('.tree-lhs-column'), $('.tree-rhs-column'));
|
|
|
+ @else
|
|
|
+ initVSplitter('stat-tree-edit-3', $('.mv-tree'), $('.mv-table'));
|
|
|
@endif
|
|
|
|
|
|
}
|