|
592 | 592 | </g>
|
593 | 593 | </g>
|
594 | 594 | </svg>
|
595 |
| -<script> |
596 |
| -/** @type {SVGGElement} */ |
597 |
| -var edges = document.querySelectorAll('.edge'); |
598 |
| -/** @type {SVGGElement} */ |
599 |
| -var nodes = document.querySelectorAll('.node'); |
600 |
| -/** @type {{[key: string]: SVGGElement}} */ |
601 |
| -var nodeMap = {}; |
602 |
| -nodes.forEach(function (n) { |
603 |
| - /** @type {SVGTitleElement} */ |
604 |
| - var title = n.querySelector('title'); |
605 |
| - var titleText = title && title.textContent; |
606 |
| - if (titleText) { |
607 |
| - nodeMap[titleText.trim()] = n; |
| 595 | +<script>document.body.onmouseover = getHighlightHandler(); |
| 596 | + |
| 597 | +function getHighlightHandler() { |
| 598 | + /** @type {string} */ |
| 599 | + var currentHighlightedTitle; |
| 600 | + |
| 601 | + /** @type {NodeListOf<SVGGElement>} */ |
| 602 | + var nodes = document.querySelectorAll(".node"); |
| 603 | + /** @type {NodeListOf<SVGGElement>} */ |
| 604 | + var edges = document.querySelectorAll(".edge"); |
| 605 | + var title2ElementMap = new Title2ElementMap(edges, nodes); |
| 606 | + |
| 607 | + /** @param {MouseEvent} pMouseEvent */ |
| 608 | + return function highlightHandler(pMouseEvent) { |
| 609 | + var closestNodeOrEdge = pMouseEvent.target.closest(".edge, .node"); |
| 610 | + var closestTitleText = getTitleText(closestNodeOrEdge); |
| 611 | + |
| 612 | + if (!(currentHighlightedTitle === closestTitleText)) { |
| 613 | + title2ElementMap.get(currentHighlightedTitle).forEach(removeHighlight); |
| 614 | + title2ElementMap.get(closestTitleText).forEach(addHighlight); |
| 615 | + currentHighlightedTitle = closestTitleText; |
| 616 | + } |
| 617 | + }; |
| 618 | +} |
| 619 | + |
| 620 | +/** |
| 621 | + * |
| 622 | + * @param {SVGGelement[]} pEdges |
| 623 | + * @param {SVGGElement[]} pNodes |
| 624 | + * @return {{get: (pTitleText:string) => SVGGElement[]}} |
| 625 | + */ |
| 626 | +function Title2ElementMap(pEdges, pNodes) { |
| 627 | + /* {{[key: string]: SVGGElement[]}} */ |
| 628 | + var elementMap = buildMap(pEdges, pNodes); |
| 629 | + |
| 630 | + /** |
| 631 | + * @param {NodeListOf<SVGGElement>} pEdges |
| 632 | + * @param {NodeListOf<SVGGElement>} pNodes |
| 633 | + * @return {{[key: string]: SVGGElement[]}} |
| 634 | + */ |
| 635 | + function buildMap(pEdges, pNodes) { |
| 636 | + var title2NodeMap = buildTitle2NodeMap(pNodes); |
| 637 | + |
| 638 | + return nodeListToArray(pEdges).reduce(addEdgeToMap(title2NodeMap), {}); |
608 | 639 | }
|
609 |
| -}); |
610 |
| -/** @type {{[key: string]: SVGGElement[]}} */ |
611 |
| -var edgeMap = {}; |
612 |
| -edges.forEach(function (e) { |
613 |
| - /** @type {SVGTitleElement} */ |
614 |
| - var title = e.querySelector('title'); |
615 |
| - var titleText = title && title.textContent; |
616 |
| - if (titleText) { |
617 |
| - titleText = titleText.trim(); |
618 |
| - var nodeNames = titleText.split(/\s*->\s*/); |
619 |
| - edgeMap[titleText] = [nodeMap[nodeNames[0]], nodeMap[nodeNames[1]]]; |
620 |
| - (edgeMap[nodeNames[0]] || (edgeMap[nodeNames[0]] = [])).push(e); |
621 |
| - (edgeMap[nodeNames[1]] || (edgeMap[nodeNames[1]] = [])).push(e); |
| 640 | + /** |
| 641 | + * @param {NodeListOf<SVGGElement>} pNodes |
| 642 | + * @return {{[key: string]: SVGGElement}} |
| 643 | + */ |
| 644 | + function buildTitle2NodeMap(pNodes) { |
| 645 | + return nodeListToArray(pNodes).reduce(addNodeToMap, {}); |
622 | 646 | }
|
623 |
| -}); |
624 | 647 |
|
625 |
| -document.body.onmouseover = onmouseover; |
| 648 | + function addNodeToMap(pMap, pNode) { |
| 649 | + var titleText = getTitleText(pNode); |
| 650 | + |
| 651 | + if (titleText) { |
| 652 | + pMap[titleText] = pNode; |
| 653 | + } |
| 654 | + return pMap; |
| 655 | + } |
| 656 | + |
| 657 | + function addEdgeToMap(pNodeMap) { |
| 658 | + return function (pEdgeMap, pEdge) { |
| 659 | + /** @type {string} */ |
| 660 | + var titleText = getTitleText(pEdge); |
| 661 | + |
| 662 | + if (titleText) { |
| 663 | + var edge = pryEdgeFromTitle(titleText); |
626 | 664 |
|
627 |
| -/** @type {string} */ |
628 |
| -var current; |
629 |
| -/** @param ev {MouseEvent} */ |
630 |
| -function onmouseover(ev) { |
631 |
| - /** @type {Element} */ |
632 |
| - var target = ev.target; |
633 |
| - /** @type {SVGGElement} */ |
634 |
| - var nodeOrEdge = target.closest('.edge, .node'); |
| 665 | + pEdgeMap[titleText] = [pNodeMap[edge.from], pNodeMap[edge.to]]; |
| 666 | + (pEdgeMap[edge.from] || (pEdgeMap[edge.from] = [])).push(pEdge); |
| 667 | + (pEdgeMap[edge.to] || (pEdgeMap[edge.to] = [])).push(pEdge); |
| 668 | + } |
| 669 | + return pEdgeMap; |
| 670 | + }; |
| 671 | + } |
| 672 | + |
| 673 | + /** |
| 674 | + * |
| 675 | + * @param {string} pString |
| 676 | + * @return {{from?: string; to?:string;}} |
| 677 | + */ |
| 678 | + function pryEdgeFromTitle(pString) { |
| 679 | + var nodeNames = pString.split(/\s*->\s*/); |
| 680 | + |
| 681 | + return { |
| 682 | + from: nodeNames.shift(), |
| 683 | + to: nodeNames.shift(), |
| 684 | + }; |
| 685 | + } |
| 686 | + /** |
| 687 | + * |
| 688 | + * @param {string} pTitleText |
| 689 | + * @return {SVGGElement[]} |
| 690 | + */ |
| 691 | + function get(pTitleText) { |
| 692 | + return (pTitleText && elementMap[pTitleText]) || []; |
| 693 | + } |
| 694 | + return { |
| 695 | + get: get, |
| 696 | + }; |
| 697 | +} |
| 698 | + |
| 699 | +/** |
| 700 | + * @param {SVGGElement} pGElement |
| 701 | + * @return {string?} |
| 702 | + */ |
| 703 | +function getTitleText(pGElement) { |
635 | 704 | /** @type {SVGTitleElement} */
|
636 |
| - var title = nodeOrEdge && nodeOrEdge.querySelector('title'); |
| 705 | + var title = pGElement && pGElement.querySelector("title"); |
| 706 | + /** @type {string} */ |
637 | 707 | var titleText = title && title.textContent;
|
| 708 | + |
638 | 709 | if (titleText) {
|
639 | 710 | titleText = titleText.trim();
|
640 | 711 | }
|
641 |
| - if (current === titleText) { |
642 |
| - return; |
643 |
| - } |
644 |
| - var old = current && edgeMap[current]; |
645 |
| - if (old) { |
646 |
| - old.forEach(function (g) { |
647 |
| - g.classList.remove('current'); |
648 |
| - }); |
| 712 | + return titleText; |
| 713 | +} |
| 714 | + |
| 715 | +/** |
| 716 | + * @param {NodeListOf<Element>} pNodeList |
| 717 | + * @return {Element[]} |
| 718 | + */ |
| 719 | +function nodeListToArray(pNodeList) { |
| 720 | + var lReturnValue = []; |
| 721 | + |
| 722 | + pNodeList.forEach(function (pElement) { |
| 723 | + lReturnValue.push(pElement); |
| 724 | + }); |
| 725 | + |
| 726 | + return lReturnValue; |
| 727 | +} |
| 728 | + |
| 729 | +/** |
| 730 | + * @param {SVGGElement} pGElement |
| 731 | + */ |
| 732 | +function removeHighlight(pGElement) { |
| 733 | + if (pGElement && pGElement.classList) { |
| 734 | + pGElement.classList.remove("current"); |
649 | 735 | }
|
650 |
| - current = titleText; |
651 |
| - var currentItems = edgeMap[current]; |
652 |
| - if (currentItems) { |
653 |
| - currentItems.forEach(function (g) { |
654 |
| - g.classList.add('current'); |
655 |
| - }); |
| 736 | +} |
| 737 | + |
| 738 | +/** |
| 739 | + * @param {SVGGElement} pGroup |
| 740 | + */ |
| 741 | +function addHighlight(pGroup) { |
| 742 | + if (pGroup && pGroup.classList) { |
| 743 | + pGroup.classList.add("current"); |
656 | 744 | }
|
657 | 745 | }
|
658 |
| -</script> |
659 |
| - </body> |
| 746 | +</script> </body> |
660 | 747 | </html>
|
0 commit comments