Skip to content

Commit 12d905f

Browse files
committed
* implement clearer and smarter loop grouping
* update preview image
1 parent 09a3bc3 commit 12d905f

File tree

2 files changed

+73
-81
lines changed

2 files changed

+73
-81
lines changed

bhav-to-diagram.html

Lines changed: 73 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,10 @@
155155
<section id="settings">
156156
<button id="tutorial-button" title="Replace the code in the BHAV textbox with a tutorial code">Show tutorial</button>
157157
<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>
163162
</select>
164163
<select id="direction-select" title="Change the diagram flow direction">
165164
<option value="TD" selected>Vertical</option>
@@ -200,6 +199,9 @@
200199
</section>
201200

202201
<script>
202+
203+
/* Interactivity setup */
204+
203205
const inputArea = document.getElementById('input-code');
204206
const loopHandlingSelect = document.getElementById('loop-handling-select');
205207
const directionSelect = document.getElementById('direction-select');
@@ -382,6 +384,8 @@
382384
svgPanZoomContainer.resetScale(diagramWrapper);
383385
}
384386

387+
/* Helper functions */
388+
385389
function removeFromArray(array, object) {
386390
array.splice(array.indexOf(object), 1);
387391
}
@@ -570,6 +574,13 @@
570574
}
571575
}
572576

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+
573584
class BhavBlockHierarchy {
574585
blocksOnThisLevel;
575586
blocksOnAllLevels;
@@ -579,8 +590,7 @@
579590
{
580591
none: 0,
581592
outerOnly: 1,
582-
similarGrouped: 2,
583-
detailed: 3
593+
nested: 2
584594
};
585595

586596
constructor(blocks, blockLoops, properties) {
@@ -604,109 +614,90 @@
604614
if (Object.values(this.constructor.loopHandlingSettings).includes(loopHandling) === false)
605615
throw new Error(`Unknown loop handling setting: '${loopHandling}'`);
606616

607-
if (loopHandling == this.constructor.loopHandlingSettings.none)
617+
if (loopHandling == this.constructor.loopHandlingSettings.none || blockLoops.length == 0)
608618
return [];
609-
610-
let loopGroups = [];
611-
let blockLoopsFromBiggest = blockLoops.toSorted((a, b) => b.length - a.length);
612619

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])]);
619622

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));
627633
return;
628634
}
629635

630-
let firstLoopGroup = matchingLoopGroups[0];
631-
blockLoop.forEach(block => firstLoopGroup.blocks.add(block));
636+
matchingMergedBlockLoop.blockSet = matchingMergedBlockLoop.blockSet.union(blockLoop.blockSet);
637+
});
632638

633-
if (matchingLoopGroups.length > 1) {
634-
firstLoopGroup.mainBlockLoops.push(blockLoop);
639+
return this.#getLoopHierarchiesRecursive(mergedBlockLoops, loopHandling);
640+
}
635641

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+
);
638652

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: []
643661
}
662+
loopGroups.push(loopGroup);
644663
return;
645664
}
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+
}
657677
});
658678

659679
if (loopHandling == this.constructor.loopHandlingSettings.outerOnly)
660680
{
661-
let blockLoopHierarchies = loopGroups.map(loopGroup => new BhavBlockLoopHierarchy(loopGroup.blocks));
681+
let blockLoopHierarchies = loopGroups.map(loopGroup => new BhavBlockLoopHierarchy([...loopGroup.blockSet]));
662682
return blockLoopHierarchies;
663683
}
664684

665685
let blockLoopHierarchies = [];
666-
let groupSimilar = loopHandling == this.constructor.loopHandlingSettings.similarGrouped;
667686
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);
694688

695689
if (subLoopHierarchies.length === 1) {
696690
let subHierarchy = subLoopHierarchies[0];
697691
let subHierarchySize = subHierarchy.blocksOnAllLevels.length;
698-
let thisHierarchySize = loopGroup.blocks.size;
692+
let thisHierarchySize = loopGroup.blockSet.size;
699693

700694
if (subHierarchySize === thisHierarchySize) {
701695
blockLoopHierarchies.push(subHierarchy);
702696
return;
703697
}
704-
if (groupSimilar && (subHierarchySize + 2 >= thisHierarchySize) && (subHierarchySize / thisHierarchySize >= 0.5)) {
705-
subLoopHierarchies = subHierarchy.subBlockLoopHierarchies;
706-
}
707698
}
708699

709-
let bhavBlockLoopHierarchy = new BhavBlockLoopHierarchy(loopGroup.blocks);
700+
let bhavBlockLoopHierarchy = new BhavBlockLoopHierarchy([...loopGroup.blockSet]);
710701
subLoopHierarchies.forEach(subLoopHierarchy => {
711702
bhavBlockLoopHierarchy.addSubBlockLoopHierarchy(subLoopHierarchy);
712703
});
@@ -751,10 +742,11 @@
751742
return;
752743

753744
if (blockChainWithThisBlock.includes(transition.endBlock)) {
754-
let firstBlockPosition = blockChainWithThisBlock.indexOf(transition.endBlock);
745+
let firstBlock = transition.endBlock;
746+
let firstBlockPosition = blockChainWithThisBlock.indexOf(firstBlock);
755747

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]));
758750
return;
759751
}
760752

readme-images/bhav-to-diagram.png

6.32 KB
Loading

0 commit comments

Comments
 (0)