636{
637
638 std::stringstream outputstream;
639
640 boost::write_graphviz(outputstream,
644
645
646
647
648
649
650
651 struct VertexStyle
652 {
653 const std::string shape;
654 const std::string color;
655 };
656
657 const std::unordered_map<ObjectKind, VertexStyle> vertex_styles{ {
ObjectKind::kSession, {
"octagon",
"black" } },
661
662 std::string dotfile_slurped = outputstream.str();
663 std::vector<std::string> legend_entries{
664 "legendGA [label=<<font color=\"black\"><b><i>⟶ Network Connection</i></b></font>>, shape=plaintext];",
665 "legendGB [label=<<font color=\"blue\"><b><i>⟶ Pub/Sub Network</i></b></font>>, shape=plaintext];"
666 };
667 std::vector<std::string> internal_legend_entries{
668 "legendGC [label=<<font color=\"green\"><b><i>⟶ Data Move Callback</i></b></font>>, shape=plaintext];",
669 "legendGD [label=<<font color=\"red\"><b><i>⟶ Queue</i></b></font>>, shape=plaintext];",
670 "legendGE [label=<<font color=\"orange\"><b><i>⟶ Queue w/ Source ID</i></b></font>>, shape=plaintext];"
671 };
672 bool internal_legend_added = false;
673 std::vector<std::string> legend_ordering_code{};
674
676
677 std::stringstream vertexstr{};
678 std::stringstream legendstr{};
679 std::stringstream labelstringstr{};
680 size_t insertion_location{ 0 };
681
682 auto calculate_insertion_location = [&]() {
683 labelstringstr << "label=\"" << eo.config_object.UID() << "\n";
684 insertion_location = dotfile_slurped.find(labelstringstr.str());
685 assert(insertion_location != std::string::npos);
686 return insertion_location;
687 };
688
689
690
691
692
693
694
695 auto add_vertex_info = [&]() {
696 vertexstr << "shape=" << vertex_styles.at(eo.kind).shape << ", color=" << vertex_styles.at(eo.kind).color
697 << ", fontcolor=" << vertex_styles.at(eo.kind).color << ", ";
698 dotfile_slurped.insert(calculate_insertion_location(), vertexstr.str());
699 };
700
701 auto add_legend_entry = [&](char letter, const std::string objkind) {
702 legendstr << "legend" << letter << " [label=<<font color=\"" << vertex_styles.at(eo.kind).color << "\"><b><i>"
703 << vertex_styles.at(eo.kind).color << ": " << objkind
704 << "</i></b></font>>, shape=plaintext, color=" << vertex_styles.at(eo.kind).color
705 << ", fontcolor=" << vertex_styles.at(eo.kind).color << "];";
706 };
707
708
709
710
711
712
713 switch (eo.kind) {
715 add_vertex_info();
716 add_legend_entry('A', "session");
717 break;
719 add_vertex_info();
720 add_legend_entry('B', "segment");
721 break;
723 add_vertex_info();
724 add_legend_entry('C', "application");
725 break;
727 add_vertex_info();
728 add_legend_entry('D', "DAQModule");
729 if (!internal_legend_added) {
730 legend_entries.insert(legend_entries.end(),
731 internal_legend_entries.begin(),
732 internal_legend_entries.end());
733 internal_legend_added = true;
734 }
735 break;
737 legendstr
738 << "legendE [label=<<font color=\"black\">O:<b><i> External Data Source</i></b></font>>, shape=plaintext];";
739 break;
741 legendstr
742 << "legendF [label=<<font color=\"black\">X:<b><i> External Data Sink</i></b></font>>, shape=plaintext];";
743 break;
744 default:
745 assert(false);
746 }
747
748 if (std::ranges::find(legend_entries, legendstr.str()) == legend_entries.end()) {
749 legend_entries.emplace_back(legendstr.str());
750 }
751 }
752
753 std::ranges::sort(legend_entries);
754
755
756
757
758
759
760
761
762
763
764
765
766 auto legend_tokens = legend_entries | std::views::transform([](const std::string& line) {
767 return line.substr(0, line.find(' '));
768 });
769
770 auto it = legend_tokens.begin();
771 for (auto next_it = std::next(it); next_it != legend_tokens.end(); ++it, ++next_it) {
772 std::stringstream astr{};
773 astr << " " << *it << " -> " << *next_it << " [style=invis];";
774 legend_ordering_code.push_back(astr.str());
775 }
776
777 constexpr int chars_to_last_brace = 2;
778 auto last_brace_iter = dotfile_slurped.end() - chars_to_last_brace;
779 assert(*last_brace_iter == '}');
780 size_t last_brace_loc = last_brace_iter - dotfile_slurped.begin();
781
782 std::string legend_code{};
783 legend_code += "\n\n\n";
784
785 for (const auto& l : legend_entries) {
786 legend_code +=
l +
"\n";
787 }
788
789 legend_code += "\n\n\n";
790
791 for (const auto& l : legend_ordering_code) {
792 legend_code +=
l +
"\n";
793 }
794
795 dotfile_slurped.insert(last_brace_loc, legend_code);
796
797
798
799
800
801
802 const std::string unlabeled_edge = "label=\"\"";
803 const std::string edge_modifier = ", style=\"dotted\", arrowhead=\"none\"";
804
805 size_t pos = 0;
806 while ((pos = dotfile_slurped.find(unlabeled_edge, pos)) != std::string::npos) {
807 dotfile_slurped.replace(pos, unlabeled_edge.length(), unlabeled_edge + edge_modifier);
808 pos += (unlabeled_edge + edge_modifier).length();
809 }
810
811
812 std::vector<std::pair<std::string, std::string>> connection_colors = { { "@NetworkConnection@kSendRecv\"", "\", color=black" },
813 { "@NetworkConnection@kPubSub\"", "\", color=blue" },
814 { "@QueueWithSourceId\"", "\", color=orange" },
815 { "@Queue\"", "\", color=red" },
816 { "@DataMoveCallbackConf\"",
817 "\", color=green" } };
818 for (auto& color_pair : connection_colors) {
819 auto conn_type = color_pair.first;
820 auto color_info = color_pair.second;
821 pos = 0;
822 while ((pos = dotfile_slurped.find(conn_type, pos)) != std::string::npos) {
823 dotfile_slurped.replace(pos, conn_type.length(), color_info);
824 pos += color_info.length();
825 }
826 }
827
828
829
830 std::ofstream outputfile;
831 outputfile.open(outputfilename);
832
833 if (outputfile.is_open()) {
834 outputfile << dotfile_slurped.c_str();
835 } else {
836 std::stringstream errmsg;
837 errmsg << "Unable to open requested file \"" << outputfilename << "\" for writing";
838 throw daqconf::GeneralGraphToolError(
ERS_HERE, errmsg.str());
839 }
840}
const std::string displaylabel
const std::string displaylabel