|
155 | 155 | <section id="settings"> |
156 | 156 | <button id="tutorial-button" title="Replace the code in the BHAV textbox with a tutorial code">Show tutorial</button> |
157 | 157 | <button id="paste-button" title="Paste the copied text from your clipboard into the BHAV textbox">Paste code</button> |
158 | | - <select id="loop-handling-select" title="Set how loops are displayed"> |
159 | | - <option value="0">Don't highlight loops</option> |
160 | | - <option value="1">Outer loops only</option> |
161 | | - <option value="2" selected>Similar loops grouped</option> |
162 | | - <option value="3">Detailed loops</option> |
| 158 | + <select id="loop-handling-select" title="Set how loops are highlighted"> |
| 159 | + <option value="0">No loops</option> |
| 160 | + <option value="1">Outer loops</option> |
| 161 | + <option value="2" selected>Nested loops</option> |
163 | 162 | </select> |
164 | 163 | <select id="direction-select" title="Change the diagram flow direction"> |
165 | 164 | <option value="TD" selected>Vertical</option> |
|
200 | 199 | </section> |
201 | 200 |
|
202 | 201 | <script> |
| 202 | + |
| 203 | + /* Interactivity setup */ |
| 204 | + |
203 | 205 | const inputArea = document.getElementById('input-code'); |
204 | 206 | const loopHandlingSelect = document.getElementById('loop-handling-select'); |
205 | 207 | const directionSelect = document.getElementById('direction-select'); |
|
382 | 384 | svgPanZoomContainer.resetScale(diagramWrapper); |
383 | 385 | } |
384 | 386 |
|
| 387 | + /* Helper functions */ |
| 388 | + |
385 | 389 | function removeFromArray(array, object) { |
386 | 390 | array.splice(array.indexOf(object), 1); |
387 | 391 | } |
|
570 | 574 | } |
571 | 575 | } |
572 | 576 |
|
| 577 | + class BlockLoop { |
| 578 | + constructor(blocksInLoop, startBlocks) { |
| 579 | + this.blockSet = blocksInLoop instanceof Set ? blocksInLoop : new Set(blocksInLoop); |
| 580 | + this.startBlockSet = startBlocks instanceof Set ? startBlocks : new Set(startBlocks); |
| 581 | + } |
| 582 | + } |
| 583 | + |
573 | 584 | class BhavBlockHierarchy { |
574 | 585 | blocksOnThisLevel; |
575 | 586 | blocksOnAllLevels; |
|
579 | 590 | { |
580 | 591 | none: 0, |
581 | 592 | outerOnly: 1, |
582 | | - similarGrouped: 2, |
583 | | - detailed: 3 |
| 593 | + nested: 2 |
584 | 594 | }; |
585 | 595 |
|
586 | 596 | constructor(blocks, blockLoops, properties) { |
|
604 | 614 | if (Object.values(this.constructor.loopHandlingSettings).includes(loopHandling) === false) |
605 | 615 | throw new Error(`Unknown loop handling setting: '${loopHandling}'`); |
606 | 616 |
|
607 | | - if (loopHandling == this.constructor.loopHandlingSettings.none) |
| 617 | + if (loopHandling == this.constructor.loopHandlingSettings.none || blockLoops.length == 0) |
608 | 618 | return []; |
609 | | - |
610 | | - let loopGroups = []; |
611 | | - let blockLoopsFromBiggest = blockLoops.toSorted((a, b) => b.length - a.length); |
612 | 619 |
|
613 | | - blockLoopsFromBiggest.forEach(blockLoop => { |
614 | | - let matchingLoopGroups = loopGroups |
615 | | - .filter(loopGroup => blockLoop |
616 | | - .some(block => loopGroup.blocks.has(block) |
617 | | - ) |
618 | | - ); |
| 620 | + let mergedBlockLoops = []; |
| 621 | + let loopStartBlockSet = new Set([...blockLoops.flatMap(blockLoop => [...blockLoop.startBlockSet])]); |
619 | 622 |
|
620 | | - if (matchingLoopGroups.length === 0) { |
621 | | - let loopGroup = { |
622 | | - blocks: new Set(blockLoop), |
623 | | - mainBlockLoops: [blockLoop], |
624 | | - subBlockLoops: [] |
625 | | - } |
626 | | - loopGroups.push(loopGroup); |
| 623 | + blockLoops.forEach(blockLoop => { |
| 624 | + let startBlockSet = loopStartBlockSet.intersection(blockLoop.blockSet); |
| 625 | + |
| 626 | + let matchingMergedBlockLoop = mergedBlockLoops.find(mergedBlockLoop => |
| 627 | + startBlockSet.size == mergedBlockLoop.startBlockSet.size |
| 628 | + && startBlockSet.isSubsetOf(mergedBlockLoop.startBlockSet) |
| 629 | + ); |
| 630 | + |
| 631 | + if (matchingMergedBlockLoop === undefined) { |
| 632 | + mergedBlockLoops.push(new BlockLoop(blockLoop.blockSet, startBlockSet)); |
627 | 633 | return; |
628 | 634 | } |
629 | 635 |
|
630 | | - let firstLoopGroup = matchingLoopGroups[0]; |
631 | | - blockLoop.forEach(block => firstLoopGroup.blocks.add(block)); |
| 636 | + matchingMergedBlockLoop.blockSet = matchingMergedBlockLoop.blockSet.union(blockLoop.blockSet); |
| 637 | + }); |
632 | 638 |
|
633 | | - if (matchingLoopGroups.length > 1) { |
634 | | - firstLoopGroup.mainBlockLoops.push(blockLoop); |
| 639 | + return this.#getLoopHierarchiesRecursive(mergedBlockLoops, loopHandling); |
| 640 | + } |
635 | 641 |
|
636 | | - for (let i = 1; i < matchingLoopGroups.length; i++) { |
637 | | - var loopGroupToMerge = matchingLoopGroups[i]; |
| 642 | + #getLoopHierarchiesRecursive(blockLoops, loopHandling) { |
| 643 | + if (blockLoops.length === 0) |
| 644 | + return []; |
| 645 | + |
| 646 | + let loopGroups = []; |
| 647 | + let blockLoopsSorted = blockLoops.toSorted((a, b) => |
| 648 | + b.startBlockSet.size == a.startBlockSet.size |
| 649 | + ? b.blockSet.size - a.blockSet.size |
| 650 | + : b.startBlockSet.size - a.startBlockSet.size |
| 651 | + ); |
638 | 652 |
|
639 | | - loopGroupToMerge.blocks.forEach(block => firstLoopGroup.add(block)); |
640 | | - firstLoopGroup.mainBlockLoops.push(...loopGroupToMerge.mainBlockLoops); |
641 | | - firstLoopGroup.subBlockLoops.push(...loopGroupToMerge.subBlockLoops); |
642 | | - removeFromArray(loopGroups, loopGroupToMerge); |
| 653 | + blockLoopsSorted.forEach(blockLoop => { |
| 654 | + let intersectingLoopGroups = loopGroups |
| 655 | + .filter(loopGroup => loopGroup.blockSet.isDisjointFrom(blockLoop.blockSet) == false); |
| 656 | + |
| 657 | + if (intersectingLoopGroups.length === 0) { |
| 658 | + let loopGroup = { |
| 659 | + blockSet: blockLoop.blockSet, |
| 660 | + subBlockLoops: [] |
643 | 661 | } |
| 662 | + loopGroups.push(loopGroup); |
644 | 663 | return; |
645 | 664 | } |
646 | | - |
647 | | - let shouldBlockLoopBeSub = firstLoopGroup.mainBlockLoops |
648 | | - .some(mainBlockLoop => |
649 | | - blockLoop.length < mainBlockLoop.length |
650 | | - && blockLoop.every(block => mainBlockLoop.includes(block)) |
651 | | - ); |
652 | | - |
653 | | - if (shouldBlockLoopBeSub) |
654 | | - firstLoopGroup.subBlockLoops.push(blockLoop); |
655 | | - else |
656 | | - firstLoopGroup.mainBlockLoops.push(blockLoop); |
| 665 | + |
| 666 | + // merge groups |
| 667 | + let firstLoopGroup = intersectingLoopGroups[0]; |
| 668 | + for (let i = 0; i < intersectingLoopGroups.length; i++) { |
| 669 | + let intersectingLoopGroup = intersectingLoopGroups[i]; |
| 670 | + |
| 671 | + intersectingLoopGroup.subBlockLoops.push(blockLoop); |
| 672 | + intersectingLoopGroup.blockSet = intersectingLoopGroup.blockSet.union(blockLoop.blockSet); |
| 673 | + |
| 674 | + if (i > 0) |
| 675 | + removeFromArray(loopGroups, intersectingLoopGroup); |
| 676 | + } |
657 | 677 | }); |
658 | 678 |
|
659 | 679 | if (loopHandling == this.constructor.loopHandlingSettings.outerOnly) |
660 | 680 | { |
661 | | - let blockLoopHierarchies = loopGroups.map(loopGroup => new BhavBlockLoopHierarchy(loopGroup.blocks)); |
| 681 | + let blockLoopHierarchies = loopGroups.map(loopGroup => new BhavBlockLoopHierarchy([...loopGroup.blockSet])); |
662 | 682 | return blockLoopHierarchies; |
663 | 683 | } |
664 | 684 |
|
665 | 685 | let blockLoopHierarchies = []; |
666 | | - let groupSimilar = loopHandling == this.constructor.loopHandlingSettings.similarGrouped; |
667 | 686 | loopGroups.forEach(loopGroup => { |
668 | | - if (loopGroup.mainBlockLoops.length > 1) { |
669 | | - // in some cases there is a main loop |
670 | | - // that could have its own hierarchy |
671 | | - // because there are no sub loops |
672 | | - // that belong to it only partly |
673 | | - // - Cosmatevs the poet |
674 | | - loopGroup.mainBlockLoops.sort((a, b) => b.length - a.length); |
675 | | - loopGroup.mainBlockLoops.forEach(mainBlockLoop => { |
676 | | - if (mainBlockLoop.length === loopGroup.blocks.size) |
677 | | - return; |
678 | | - |
679 | | - for (let i = 0; i < loopGroup.subBlockLoops.length; i++) { |
680 | | - let subBlockLoop = loopGroup.subBlockLoops[i]; |
681 | | - // is any sub block in main block loop |
682 | | - if (subBlockLoop.some(subBlock => mainBlockLoop.includes(subBlock)) === false) |
683 | | - continue; |
684 | | - // is any sub block not in main block loop |
685 | | - if (subBlockLoop.some(subBlock => mainBlockLoop.includes(subBlock) === false)) |
686 | | - return; |
687 | | - } |
688 | | - |
689 | | - loopGroup.subBlockLoops.push(mainBlockLoop); |
690 | | - }); |
691 | | - } |
692 | | - |
693 | | - let subLoopHierarchies = this.#getLoopHierarchies(loopGroup.subBlockLoops, loopHandling); |
| 687 | + let subLoopHierarchies = this.#getLoopHierarchiesRecursive(loopGroup.subBlockLoops, loopHandling); |
694 | 688 |
|
695 | 689 | if (subLoopHierarchies.length === 1) { |
696 | 690 | let subHierarchy = subLoopHierarchies[0]; |
697 | 691 | let subHierarchySize = subHierarchy.blocksOnAllLevels.length; |
698 | | - let thisHierarchySize = loopGroup.blocks.size; |
| 692 | + let thisHierarchySize = loopGroup.blockSet.size; |
699 | 693 |
|
700 | 694 | if (subHierarchySize === thisHierarchySize) { |
701 | 695 | blockLoopHierarchies.push(subHierarchy); |
702 | 696 | return; |
703 | 697 | } |
704 | | - if (groupSimilar && (subHierarchySize + 2 >= thisHierarchySize) && (subHierarchySize / thisHierarchySize >= 0.5)) { |
705 | | - subLoopHierarchies = subHierarchy.subBlockLoopHierarchies; |
706 | | - } |
707 | 698 | } |
708 | 699 |
|
709 | | - let bhavBlockLoopHierarchy = new BhavBlockLoopHierarchy(loopGroup.blocks); |
| 700 | + let bhavBlockLoopHierarchy = new BhavBlockLoopHierarchy([...loopGroup.blockSet]); |
710 | 701 | subLoopHierarchies.forEach(subLoopHierarchy => { |
711 | 702 | bhavBlockLoopHierarchy.addSubBlockLoopHierarchy(subLoopHierarchy); |
712 | 703 | }); |
|
751 | 742 | return; |
752 | 743 |
|
753 | 744 | if (blockChainWithThisBlock.includes(transition.endBlock)) { |
754 | | - let firstBlockPosition = blockChainWithThisBlock.indexOf(transition.endBlock); |
| 745 | + let firstBlock = transition.endBlock; |
| 746 | + let firstBlockPosition = blockChainWithThisBlock.indexOf(firstBlock); |
755 | 747 |
|
756 | | - let blockLoop = blockChainWithThisBlock.slice(firstBlockPosition); |
757 | | - blockOutcome.blockLoops.push(blockLoop); |
| 748 | + let blocksInLoop = blockChainWithThisBlock.slice(firstBlockPosition); |
| 749 | + blockOutcome.blockLoops.push(new BlockLoop(blocksInLoop, [firstBlock])); |
758 | 750 | return; |
759 | 751 | } |
760 | 752 |
|
|
0 commit comments