diff --git a/N.js b/N.js new file mode 100644 index 0000000..cbf01f4 --- /dev/null +++ b/N.js @@ -0,0 +1,173 @@ +var N = +{ + ID:{ + Walk:0, + Instance:0 + }, + + Create(inMeta) + { + return { + ID:{ + Walk:0, + Instance:N.ID.Instance++ + }, + Meta:inMeta||{}, + Link:{} + }; + }, + Connect(inNodeMajor, inNodeMinor, inKey, inUnique) + { + if(inUnique) // bail if the nodes are already connected + { + let check = N.Step(inNodeMajor, inKey, true); + if(check) + { + if(check.indexOf(inNodeMinor) !== -1) + { + return; + } + } + } + N.Step(inNodeMajor, inKey, true, true).push(inNodeMinor); + N.Step(inNodeMinor, inKey, false, true).push(inNodeMajor); + }, + Disconnect(inNodeMajor, inNodeMinor, inKey) + { + let remove = (inArray, inMatch) => inArray.findIndex( (inMember, inIndex, inArray) => (inMember === inMatch) ? inArray.splice(inIndex, 1) : false ); + + // if no specific child was passed + if(inNodeMinor === null) + { + // get all the children + let check = N.Step(inNodeMajor, inKey); + if(!check){ return; } + + // go down to each child ... + check.forEach( inNodeMinor => + { + let connections = inNodeMinor.Link[inKey]; + remove( connections.Get, inNodeMajor); // ... and remove any reference to the parent + + // if after the remove operation, this child has no connections on inKey, scrub the key + if(!connections.Set.length && !connections.Get.length) + { + delete inNodeMinor.Link[inKey]; + } + }); + + // we just wiped out all outgoing connections to the parent, if incoming connections are empty too we can purge the key there as well + if(inNodeMajor.Link[inKey].Get.length == 0) + { + delete inNodeMajor.Link[inKey]; + } + return; + } + + // if no specific parent was passed + if(inNodeMajor === null) + { + // get all the parents + let check = N.Step(inNodeMinor, inKey, false); + if(!check){ return; } + + // go up to each parent ... + check.forEach( inNodeMajor => + { + let connections = inNodeMajor.Link[inKey]; + remove( connections.Set, inNodeMinor); // ... and remove any reference to the child + + // if after the remove operation, this parent has no connections on inKey, scrub the key + if( !connections.Set.length && !connections.Get.length ) + { + delete inNodeMajor.Link[inKey]; + } + }); + + // we just wiped out all incoming connections to the child, if outgoing connections are empty too we can purge the key there as well + if(inNodeMinor.Link[inKey].Set.length == 0) + { + delete inNodeMinor.Link[inKey]; + } + return; + } + + // if a specific parent and child were passed + if(inNodeMajor.Link[inKey].Set.length == 1) + { + delete inNodeMajor.Link[inKey]; + } + else + { + remove(inNodeMajor.Link[inKey].Set, inNodeMinor); + } + if(inNodeMinor.Link[inKey].Get.length == 1) + { + delete inNodeMinor.Link[inKey]; + } + else + { + remove(inNodeMinor.Link[inKey].Get, inNodeMajor); + } + + }, + Step(inNode, inKey, inForward, inForceCreate) + { + let connectionGroup = inNode.Link[inKey]; + if(!connectionGroup) + { + if(inForceCreate === true) + { + inNode.Link[inKey] = connectionGroup = {Get:[], Set:[]}; + } + else + { + return false; + } + } + return (inForward === undefined || inForward === true) ? connectionGroup.Set : connectionGroup.Get; + + }, + Walk(inIterator, inNode, inKey, inForward, inTerminal) + { + let array = N.Step(inNode, inKey, inForward); + + if(!array.length && inTerminal) + { + return inTerminal(inNode); + } + + for(let i=0; i N.Create({Row:r})); + Pivot.Init = ()=>{}; + }, + Create(inPivotIndicies, inSumIndicies) + { + N.ID.Walk++; + + let columns = N.Step(Pivot.Schema, "all"); + let label = inPivotIndicies.map( inPivotIndex => + { + return columns[inPivotIndex].Meta.Label; + }); + + let pivotRoot = N.Create({Label:label.join("|"), Leaves:Pivot.Leaves}); + N.Connect(Pivot.Root, pivotRoot, "Pivot"); + return Pivot.Pivot(pivotRoot, pivotRoot, inPivotIndicies, inSumIndicies); + }, + Pivot(inRoot, inParent, inPivotIndicies, inSumIndicies, inDepth) + { + //arguments: + // - a Node with leaf Nodes temporarily stored in its Meta.Leaves + // - where each leaf Node has a row of table data in it's Meta.Row + // - a list of columns to pivot on + // - a list of columns to sum + // - optional traversal depth, defaults to 0 + let depth = inDepth||0; + let uniques = {}; + let indexPivot = inPivotIndicies[depth]; + if(!inParent.Meta.Leaves) + { + console.log("problem") + } + inParent.Meta.Leaves.forEach((inLeaf)=> + { + let row = inLeaf.Meta.Row; // shorthand for the raw "CSV" row in the leaf Node's Meta + let value = row[indexPivot]; // get the pivot column + let match = uniques[value]; // check in the uniques list if this pivot column exists + if(!match) + { + // if not, store a value under that key that will be the meta object for a new child + + let clone = row.map(r=>null); + inSumIndicies.forEach((inSumIndex, inIndex, inArray)=> + { + clone[inSumIndex] = row[inSumIndex] + }); + match = uniques[value] = { + Label:value, + Row:clone, + IndexPivot:indexPivot, + IndexSum:inSumIndicies, + Leaves:[], + Depth:depth + }; + // grow a child off of the parent using the meta object + N.Connect(inParent, N.Create(match), "Hierarchy"); + } + else + { + // if a match does exist, sum the appropriate columns + inSumIndicies.forEach((inSumIndex) => match.Row[inSumIndex] += row[inSumIndex]); + } + // move the leaves into the child + match.Leaves.push(inLeaf); + }); + + // get the leaves out of the parent, at this point they have been re-distributed to the children + delete inParent.Meta.Leaves; + let iterator = () => {}; + if(depth >= inPivotIndicies.length-1) + { + iterator = inLastBranch => + { + inLastBranch.Meta.Leaves.forEach( inLeaf => + { + // collect modifiers effecting leaves + let modifiers = []; + let collectModifier = n => modifiers.push(n); + let connectModifiers = n => + { + modifiers.forEach(inModifier => N.Connect(inModifier, n, "ModifyOut", true)); + }; + + N.Walk(collectModifier, inLeaf, "ModifyAt", false); + N.Walk(collectModifier, inLeaf, "ModifyDown", false); + + + if(modifiers.length) + { + // apply them to the branch + inLastBranch.ID.Walk = N.ID.Walk; + connectModifiers(inLastBranch); + + // also walk them up and connect, but with "check unique" enabled + console.log("walking modifiers up from", inLastBranch.Meta.Label); + N.ID.Walk++; + N.Walk(connectModifiers, inLastBranch, "Hierarchy", false); + /* + N.Walk((n)=> + { + console.log("going up", n); + } + , inLastBranch, "Hierarchy", false); + */ + + } + + // lastly connect the leaf to the branch + N.Connect(inLastBranch, inLeaf, "Leaf"); + + }); + delete inLastBranch.Meta.Leaves; + } + } + else + { + iterator = child => { + if(child.Meta.Label == "b") + { + console.log("at b for some reason"); + } + Pivot.Pivot(inRoot, child, inPivotIndicies, inSumIndicies, depth+1); + }; + } + N.Walk(iterator, inParent, "Hierarchy"); + + return inParent; + }, + Delete(inRoot) + { + // disconnect modifiers + let check = N.Step(inRoot, "ModifyUp", false); + if(check) + { + while(check.length>0) + { + Pivot.Unmodify(check[0]); + } + } + + // disconnect leaves from terminal branches + N.Walk(()=>{}, inRoot, "Hierarchy", true, terminal=>{ + N.Disconnect(terminal, null, "Leaf"); + }); + + // disconnect from app + N.Disconnect(null, inRoot, "Pivot"); + }, + Modify(inNode) + { + let modified = N.Create({Label:"Modifier"}); + + // traverse + let gatherUp = n => N.Connect(modified, n, "ModifyUp"); + let gatherDown = n => N.Connect(modified, n, "ModifyDown"); + let gatherOut = n => N.Connect(modified, n, "ModifyOut"); + + N.ID.Walk++; + inNode.ID.Walk = N.ID.Walk; + + // at + N.Connect(modified, inNode, "ModifyAt"); + + // up + N.Walk(gatherUp, inNode, "Hierarchy", false); + + // down 1 + N.Walk(gatherDown, inNode, "Hierarchy", true, terminal=> + { + // down 2 + // for each terminal node, step down into its leaves and gather down + N.Walk(gatherDown, terminal, "Leaf", true, leaf=> + { + // out 1 + // walk back up on the leaf connections on other trees + N.Walk(gatherOut, leaf, "Leaf", false, terminal=> + { + // out 2 + // and continueup the hierarchy + N.Walk(gatherOut, terminal, "Hierarchy", false); + }); + }); + }); + + return modified; + }, + Unmodify(inModifier) + { + N.Disconnect(inModifier, null, "ModifyUp"); + N.Disconnect(inModifier, null, "ModifyDown"); + N.Disconnect(inModifier, null, "ModifyOut"); + N.Disconnect(inModifier, null, "ModifyAt"); + N.Disconnect(null, inModifier, "Modifier"); + } +}; diff --git a/index.html b/index.html index 1fa33eb..008aa83 100755 --- a/index.html +++ b/index.html @@ -1,206 +1,410 @@ -
- - - + + + +
- }, - Walk(inIterator, inNode, inKey, inForward, inTerminal) + + + - + + let ModificationsIcon = ({node}) => + { + let modsUp = N.Step(node, "ModifyUp", false)||[]; + let modsDown = N.Step(node, "ModifyDown", false)||[]; + let modsAt = N.Step(node, "ModifyAt", false)||[]; + let modsOut = N.Step(node, "ModifyOut", false)||[]; + + + let padding = 7; + let icon = 0; + let styles = css` + position:relative; + display:inline-block; + vertical-align:middle; + width:${padding*2 + icon}px; + height:${padding*2 + icon}px; + margin:${padding}; + .Icon + { + position:absolute; + display:inline-block; + width:${padding*2 + icon}px; + height:${padding*2 + icon}px; + text-align:center; + font-size:9px; + font-family:sans-serif; + font-weight:900; + line-height:${padding*2 + icon}px; + + &::after + { + content:" "; + display:block; + position:absolute; + width:${icon}px; + height:${icon}px; + border:${padding}px solid transparent; + } + + &.Down + { + left:0; + bottom:100%; + &::after + { + top:100%; + border-top-color:green; + border-bottom:0px solid transparent; + } + } + &.At + { + top:0; + left:100%; + &::after + { + top:0; + right:100%; + border-right-color:red; + border-left:0px solid transparent; + } + } + &.Up + { + left:0; + top:100%; + &::after + { + bottom:100%; + border-bottom-color:orange; + border-top:0px solid transparent; + } + } + + &.Out + { + top:0; + right:100%; + &::after + { + top:0; + left:100%; + border-left-color:grey; + border-right:0px solid transparent; + } + } + } + + `; + + return html` +
+ ${modsDown.length ? html`
${modsDown.length}
` : null} + ${modsAt.length ? html`${modsAt.length}` : null} + ${modsUp.length ? html`${modsUp.length}` : null} + ${modsOut.length ? html`${modsOut.length}` : null} +
+ `; + + }; + + let PivotBranch = props => + { + let row = props.node.Meta.Row; + let displayCells = row.map(column=>h("td", null, column)); + let displayCellsModify = row.map(column=>false); + props.node.Meta.IndexSum.forEach(i=> + { + displayCellsModify[i] = html``; + }); + displayCellsModify.forEach((cell, i)=> + { + if(!cell) + { + displayCellsModify[i] = html`${row[i]}` + } + }); + + let modifier = N.Step(props.node, "ModifyAt", false); + let modifierDisplay = false; + if(modifier) + { + modifierDisplay = html``; + } + else + { + modifierDisplay = html``; + } + + return html` + + + + <${ModificationsIcon} node=${props.node}> + ${props.node.Meta.Label} + ${modifierDisplay} + + ${displayCells} + + + + `; + }; + + let PivotRoot = ({pivot}) => + { + + let stylesRoot = css` + display:block; + box-sizing:border-box; + padding:15px; + font-family:sans-serif; + `; + let stylesHeading = css` + margin: 0 0 15px 0; + font-weight:0; + font-size:24px; + `; + + let modifiers = N.Step(pivot, "ModifyUp", false) || []; + let handleDelete = ()=> + { + Pivot.Delete(pivot); + Render(); + }; + + let rows = []; + N.ID.Walk++; + N.Walk(n=>rows.push(h(PivotBranch, {node:n}, null)), pivot, "Hierarchy"); + + return html` +
+
${pivot.Meta.Label}
+ <${Section} key="settings" label=${`Settings`}> + + + <${Section} key="tree" label=${"Tree"}> + + ${rows} +
+ +
+ `; + }; + + let ElRoot = props => + { + let pivots = N.Step(Pivot.Root, "Pivot")||[]; + return h("div", null, [ + h(PivotForm), + pivots.map(pivot=>h(PivotRoot, {key:pivot.Meta.Label, pivot})) + ]) + }; + Render = () => render(h(ElRoot), document.querySelector("#app")); + Render(); + + - \ No newline at end of file +