-module(yog@render@dot). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/yog/render/dot.gleam"). -export([default_dot_options/0, default_dot_options_with_edge_formatter/1, default_dot_options_with/2, path_to_dot_options/2, to_dot/2]). -export_type([subgraph/0, layout/0, rank_dir/0, node_shape/0, style/0, splines/0, arrow_style/0, overlap/0, dot_options/2]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " DOT (Graphviz) format export for visualizing graphs.\n" "\n" " This module exports graphs to the [DOT language](https://graphviz.org/doc/info/lang.html),\n" " which is the native format for [Graphviz](https://graphviz.org/) - a powerful open-source\n" " graph visualization tool. The exported files can be rendered to PNG, SVG, PDF, and other\n" " formats using the `dot`, `neato`, `circo`, or other Graphviz layout engines.\n" "\n" " ## Quick Start\n" "\n" " ```gleam\n" " import yog/render/dot\n" "\n" " // Export with default styling\n" " let dot_string = dot.to_dot(my_graph, dot.default_dot_options())\n" "\n" " // Write to file and render with Graphviz CLI\n" " // $ dot -Tpng output.dot -o graph.png\n" " ```\n" "\n" " ## Customization\n" "\n" " Use `DotOptions` to customize:\n" " - Node labels and shapes\n" " - Edge labels and styles\n" " - **Per-node and per-edge attributes** (custom colors, shapes, etc.)\n" " - **Subgraphs/clusters** for visual grouping\n" " - Highlight specific nodes or paths\n" " - Graph direction (LR, TB, etc.)\n" "\n" " ## Generic Data Types\n" "\n" " The `to_dot` function works with any node and edge data types. Use \n" " `default_dot_options_with_edge_formatter()` when your edge data is not a String:\n" "\n" " ```gleam\n" " let options = dot.default_dot_options_with_edge_formatter(fn(weight) {\n" " int.to_string(weight)\n" " })\n" " let dot_string = dot.to_dot(my_int_weighted_graph, options)\n" " ```\n" "\n" " ## Per-Element Styling\n" "\n" " Provide custom attribute functions for fine-grained control:\n" "\n" " ```gleam\n" " let options = DotOptions(\n" " ..dot.default_dot_options(),\n" " node_attributes: fn(id, data) {\n" " case id {\n" " 1 -> [(\"fillcolor\", \"green\"), (\"shape\", \"diamond\")]\n" " _ -> []\n" " }\n" " },\n" " edge_attributes: fn(from, to, weight) {\n" " case weight > 10 {\n" " True -> [(\"color\", \"red\"), (\"penwidth\", \"2\")]\n" " False -> []\n" " }\n" " },\n" " )\n" " ```\n" "\n" " ## Subgraphs and Clusters\n" "\n" " Group nodes visually using subgraphs:\n" "\n" " ```gleam\n" " let options = DotOptions(\n" " ..dot.default_dot_options(),\n" " subgraphs: Some([\n" " Subgraph(\n" " name: \"cluster_0\",\n" " label: Some(\"Cluster A\"),\n" " node_ids: [1, 2, 3],\n" " style: Some(dot.Filled),\n" " fillcolor: Some(\"lightgrey\"),\n" " color: None,\n" " ),\n" " ]),\n" " )\n" " ```\n" "\n" " ## Rendering Options\n" "\n" " | Engine | Best For |\n" " |--------|----------|\n" " | `dot` | Hierarchical layouts (DAGs, trees) |\n" " | `neato` | Spring-based layouts (undirected) |\n" " | `circo` | Circular layouts |\n" " | `fdp` | Force-directed layouts |\n" " | `sfdp` | Large graphs |\n" "\n" " ## References\n" "\n" " - [Graphviz Documentation](https://graphviz.org/documentation/)\n" " - [DOT Language Guide](https://graphviz.org/doc/info/lang.html)\n" " - [Node Shapes](https://graphviz.org/doc/info/shapes.html)\n" " - [Arrow Styles](https://graphviz.org/doc/info/arrows.html)\n" " - [Cluster/Subgraph Syntax](https://graphviz.org/docs/attrs/cluster/)\n" ). -type subgraph() :: {subgraph, binary(), gleam@option:option(binary()), list(integer()), gleam@option:option(style()), gleam@option:option(binary()), gleam@option:option(binary())}. -type layout() :: dot | neato | circo | fdp | sfdp | twopi | osage | {custom_layout, binary()}. -type rank_dir() :: top_to_bottom | left_to_right | bottom_to_top | right_to_left. -type node_shape() :: box | circle | ellipse | diamond | hexagon | pentagon | octagon | triangle | rectangle | square | rect | inv_triangle | house | inv_house | parallelogram | trapezoid | {custom_shape, binary()}. -type style() :: solid | dashed | dotted | bold | filled | rounded | diagonals | striped | wedged. -type splines() :: line | polyline | curved | ortho | spline | splines_none. -type arrow_style() :: normal | arrow_dot | arrow_diamond | o_diamond | arrow_box | crow | vee | inv | tee | arrow_none | {custom_arrow, binary()}. -type overlap() :: overlap_true | overlap_false | scale | scale_x_y | prism | {custom_overlap, binary()}. -type dot_options(ACNP, ACNQ) :: {dot_options, fun((integer(), ACNP) -> binary()), fun((ACNQ) -> binary()), gleam@option:option(list(integer())), gleam@option:option(list({integer(), integer()})), fun((integer(), ACNP) -> list({binary(), binary()})), fun((integer(), integer(), ACNQ) -> list({binary(), binary()})), gleam@option:option(list(subgraph())), binary(), gleam@option:option(layout()), gleam@option:option(rank_dir()), gleam@option:option(binary()), gleam@option:option(splines()), gleam@option:option(overlap()), gleam@option:option(float()), gleam@option:option(float()), node_shape(), binary(), style(), binary(), integer(), binary(), binary(), style(), binary(), integer(), float(), gleam@option:option(arrow_style()), gleam@option:option(arrow_style()), binary(), float()}. -file("src/yog/render/dot.gleam", 455). -spec create_dot_options( fun((integer(), ACOC) -> binary()), fun((ACOD) -> binary()) ) -> dot_options(ACOC, ACOD). create_dot_options(Node_label, Edge_label) -> {dot_options, Node_label, Edge_label, none, none, fun(_, _) -> [] end, fun(_, _, _) -> [] end, none, <<"G"/utf8>>, none, {some, top_to_bottom}, none, none, none, none, none, ellipse, <<"lightblue"/utf8>>, filled, <<"Helvetica"/utf8>>, 12, <<"black"/utf8>>, <<"black"/utf8>>, solid, <<"Helvetica"/utf8>>, 10, 1.0, none, none, <<"red"/utf8>>, 2.0}. -file("src/yog/render/dot.gleam", 401). ?DOC( " Creates default DOT options with simple labeling and sensible styling.\n" "\n" " Default configuration:\n" " - Layout: Auto-detected by Graphviz\n" " - Direction: Top-to-bottom\n" " - Node shape: Ellipse\n" " - Colors: Light blue nodes, black edges\n" " - Font: Helvetica 12pt\n" "\n" " **Note:** This function returns `DotOptions(n, String)`, meaning it works\n" " with any node data type (node labels use the ID only) but requires edge\n" " data to be `String`. For other edge types, use\n" " `default_dot_options_with_edge_formatter()`.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let options = dot.default_dot_options()\n" " let dot_string = dot.to_dot(my_string_graph, options)\n" " ```\n" ). -spec default_dot_options() -> dot_options(any(), binary()). default_dot_options() -> create_dot_options( fun(Id, _) -> erlang:integer_to_binary(Id) end, fun(Weight) -> Weight end ). -file("src/yog/render/dot.gleam", 426). ?DOC( " Creates default DOT options with a custom edge formatter.\n" "\n" " Use this when your graph has non-String edge data (e.g., Int, Float, custom types).\n" " The provided formatter function converts edge data to strings for display.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " // For a graph with Int edge weights\n" " let options = dot.default_dot_options_with_edge_formatter(fn(weight) {\n" " int.to_string(weight)\n" " })\n" "\n" " // For a graph with custom edge data\n" " let options = dot.default_dot_options_with_edge_formatter(fn(edge_data) {\n" " edge_data.name <> \": \" <> float.to_string(edge_data.weight)\n" " })\n" " ```\n" ). -spec default_dot_options_with_edge_formatter(fun((ACNU) -> binary())) -> dot_options(any(), ACNU). default_dot_options_with_edge_formatter(Edge_formatter) -> create_dot_options( fun(Id, _) -> erlang:integer_to_binary(Id) end, Edge_formatter ). -file("src/yog/render/dot.gleam", 447). ?DOC( " Creates default DOT options with custom label formatters for both nodes and edges.\n" "\n" " Use this when you need full control over how both node and edge data are displayed.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let options = dot.default_dot_options_with(\n" " node_label: fn(id, data) { data.name <> \" (\" <> int.to_string(id) <> \")\" },\n" " edge_label: fn(weight) { int.to_string(weight) <> \" ms\" },\n" " )\n" " ```\n" ). -spec default_dot_options_with( fun((integer(), ACNY) -> binary()), fun((ACNZ) -> binary()) ) -> dot_options(ACNY, ACNZ). default_dot_options_with(Node_label, Edge_label) -> create_dot_options(Node_label, Edge_label). -file("src/yog/render/dot.gleam", 772). -spec merge_attributes_list( list({binary(), binary()}), list({binary(), binary()}) ) -> list({binary(), binary()}). merge_attributes_list(Base, Override) -> gleam@list:fold( Override, Base, fun(Acc, Pair) -> Filtered = gleam@list:filter( Acc, fun(Existing) -> erlang:element(1, Existing) /= erlang:element(1, Pair) end ), [Pair | Filtered] end ). -file("src/yog/render/dot.gleam", 797). -spec escape_quotes(binary()) -> binary(). escape_quotes(S) -> gleam@string:replace(S, <<"\""/utf8>>, <<"\\\""/utf8>>). -file("src/yog/render/dot.gleam", 786). -spec format_attributes_list(list({binary(), binary()})) -> binary(). format_attributes_list(Attrs) -> _pipe = Attrs, _pipe@1 = lists:reverse(_pipe), _pipe@2 = gleam@list:map( _pipe@1, fun(Pair) -> Key = erlang:element(1, Pair), Val = escape_quotes(erlang:element(2, Pair)), <<<<<>/binary, Val/binary>>/binary, "\""/utf8>> end ), gleam@string:join(_pipe@2, <<", "/utf8>>). -file("src/yog/render/dot.gleam", 836). -spec do_path_to_edges(list(integer()), list({integer(), integer()})) -> list({integer(), integer()}). do_path_to_edges(Nodes, Acc) -> case Nodes of [] -> lists:reverse(Acc); [_] -> lists:reverse(Acc); [First, Second | Rest] -> do_path_to_edges([Second | Rest], [{First, Second} | Acc]) end. -file("src/yog/render/dot.gleam", 832). -spec path_to_edges(list(integer())) -> list({integer(), integer()}). path_to_edges(Nodes) -> do_path_to_edges(Nodes, []). -file("src/yog/render/dot.gleam", 817). ?DOC( " Converts a shortest path result to highlighted DOT options.\n" "\n" " Creates a copy of the base options with the path's nodes and edges\n" " set to be highlighted. This is useful for visualizing algorithm results.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " case pathfinding.dijkstra(...) {\n" " Some(path) -> {\n" " let options = dot.path_to_dot_options(path, dot.default_dot_options())\n" " let dot_string = dot.to_dot(graph, options)\n" " }\n" " None -> \"\"\n" " }\n" " ```\n" ). -spec path_to_dot_options( yog@pathfinding@path:path(ACOQ), dot_options(ACOS, ACOQ) ) -> dot_options(ACOS, ACOQ). path_to_dot_options(Path, Base_options) -> Nodes = erlang:element(2, Path), Edges = path_to_edges(Nodes), {dot_options, erlang:element(2, Base_options), erlang:element(3, Base_options), {some, Nodes}, {some, Edges}, erlang:element(6, Base_options), erlang:element(7, Base_options), erlang:element(8, Base_options), erlang:element(9, Base_options), erlang:element(10, Base_options), erlang:element(11, Base_options), erlang:element(12, Base_options), erlang:element(13, Base_options), erlang:element(14, Base_options), erlang:element(15, Base_options), erlang:element(16, Base_options), erlang:element(17, Base_options), erlang:element(18, Base_options), erlang:element(19, Base_options), erlang:element(20, Base_options), erlang:element(21, Base_options), erlang:element(22, Base_options), erlang:element(23, Base_options), erlang:element(24, Base_options), erlang:element(25, Base_options), erlang:element(26, Base_options), erlang:element(27, Base_options), erlang:element(28, Base_options), erlang:element(29, Base_options), erlang:element(30, Base_options), erlang:element(31, Base_options)}. -file("src/yog/render/dot.gleam", 852). ?DOC(" Convert Layout to Graphviz string\n"). -spec layout_to_string(layout()) -> binary(). layout_to_string(Layout) -> case Layout of dot -> <<"dot"/utf8>>; neato -> <<"neato"/utf8>>; circo -> <<"circo"/utf8>>; fdp -> <<"fdp"/utf8>>; sfdp -> <<"sfdp"/utf8>>; twopi -> <<"twopi"/utf8>>; osage -> <<"osage"/utf8>>; {custom_layout, S} -> S end. -file("src/yog/render/dot.gleam", 866). ?DOC(" Convert RankDir to Graphviz string\n"). -spec rankdir_to_string(rank_dir()) -> binary(). rankdir_to_string(Rd) -> case Rd of top_to_bottom -> <<"TB"/utf8>>; left_to_right -> <<"LR"/utf8>>; bottom_to_top -> <<"BT"/utf8>>; right_to_left -> <<"RL"/utf8>> end. -file("src/yog/render/dot.gleam", 876). ?DOC(" Convert NodeShape to Graphviz string\n"). -spec node_shape_to_string(node_shape()) -> binary(). node_shape_to_string(Shape) -> case Shape of box -> <<"box"/utf8>>; circle -> <<"circle"/utf8>>; ellipse -> <<"ellipse"/utf8>>; diamond -> <<"diamond"/utf8>>; hexagon -> <<"hexagon"/utf8>>; pentagon -> <<"pentagon"/utf8>>; octagon -> <<"octagon"/utf8>>; triangle -> <<"triangle"/utf8>>; rectangle -> <<"rectangle"/utf8>>; square -> <<"square"/utf8>>; rect -> <<"rect"/utf8>>; inv_triangle -> <<"invtriangle"/utf8>>; house -> <<"house"/utf8>>; inv_house -> <<"invhouse"/utf8>>; parallelogram -> <<"parallelogram"/utf8>>; trapezoid -> <<"trapezoid"/utf8>>; {custom_shape, S} -> S end. -file("src/yog/render/dot.gleam", 899). ?DOC(" Convert Style to Graphviz string\n"). -spec style_to_string(style()) -> binary(). style_to_string(Style) -> case Style of solid -> <<"solid"/utf8>>; dashed -> <<"dashed"/utf8>>; dotted -> <<"dotted"/utf8>>; bold -> <<"bold"/utf8>>; filled -> <<"filled"/utf8>>; rounded -> <<"rounded"/utf8>>; diagonals -> <<"diagonals"/utf8>>; striped -> <<"striped"/utf8>>; wedged -> <<"wedged"/utf8>> end. -file("src/yog/render/dot.gleam", 914). ?DOC(" Convert Splines to Graphviz string\n"). -spec splines_to_string(splines()) -> binary(). splines_to_string(Splines) -> case Splines of line -> <<"line"/utf8>>; polyline -> <<"polyline"/utf8>>; curved -> <<"curved"/utf8>>; ortho -> <<"ortho"/utf8>>; spline -> <<"spline"/utf8>>; splines_none -> <<"none"/utf8>> end. -file("src/yog/render/dot.gleam", 926). ?DOC(" Convert ArrowStyle to Graphviz string\n"). -spec arrow_style_to_string(arrow_style()) -> binary(). arrow_style_to_string(Arrow) -> case Arrow of normal -> <<"normal"/utf8>>; arrow_dot -> <<"dot"/utf8>>; arrow_diamond -> <<"diamond"/utf8>>; o_diamond -> <<"odiamond"/utf8>>; arrow_box -> <<"box"/utf8>>; crow -> <<"crow"/utf8>>; vee -> <<"vee"/utf8>>; inv -> <<"inv"/utf8>>; tee -> <<"tee"/utf8>>; arrow_none -> <<"none"/utf8>>; {custom_arrow, S} -> S end. -file("src/yog/render/dot.gleam", 943). ?DOC(" Convert Overlap to Graphviz string\n"). -spec overlap_to_string(overlap()) -> binary(). overlap_to_string(Overlap) -> case Overlap of overlap_true -> <<"true"/utf8>>; overlap_false -> <<"false"/utf8>>; scale -> <<"scale"/utf8>>; scale_x_y -> <<"scalexy"/utf8>>; prism -> <<"prism"/utf8>>; {custom_overlap, S} -> S end. -file("src/yog/render/dot.gleam", 552). ?DOC( " Converts a graph to DOT (Graphviz) syntax.\n" "\n" " Works with any node data type `n` and edge data type `e`. Use the options\n" " to customize labels, styling, and to define subgraphs. Use\n" " `default_dot_options()` or `default_dot_options_with_edge_formatter()` to\n" " create appropriate options for your graph.\n" "\n" " **Time Complexity:** O(V + E + S) where S is the total number of nodes\n" " across all subgraphs.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let graph =\n" " model.new(Directed)\n" " |> model.add_node(1, \"Start\")\n" " |> model.add_node(2, \"Process\")\n" " |> model.add_edge(from: 1, to: 2, with: \"5\")\n" "\n" " let diagram = dot.to_dot(graph, dot.default_dot_options())\n" " // io.println(diagram)\n" " ```\n" "\n" " ## Custom Styling Example\n" "\n" " ```gleam\n" " let options = DotOptions(\n" " ..dot.default_dot_options(),\n" " node_attributes: fn(id, data) {\n" " case id {\n" " 1 -> [(\"fillcolor\", \"green\"), (\"shape\", \"diamond\")]\n" " _ -> []\n" " }\n" " },\n" " subgraphs: Some([\n" " Subgraph(name: \"cluster_0\", label: Some(\"Group A\"), node_ids: [1, 2]),\n" " ]),\n" " )\n" " ```\n" "\n" " This output can be processed by Graphviz tools (e.g., `dot -Tpng -o graph.png`):\n" " ````dot\n" " digraph G {\n" " node [shape=ellipse];\n" " 1 [label=\"Start\"];\n" " 2 [label=\"Process\"];\n" " subgraph cluster_0 {\n" " label=\"Group A\";\n" " 1; 2;\n" " }\n" " 1 -> 2 [label=\"5\"];\n" " }\n" " ````\n" ). -spec to_dot(yog@model:graph(ACOG, ACOH), dot_options(ACOG, ACOH)) -> binary(). to_dot(Graph, Options) -> Graph_type = case erlang:element(2, Graph) of directed -> <<<<"digraph "/utf8, (erlang:element(9, Options))/binary>>/binary, " {\n"/utf8>>; undirected -> <<<<"graph "/utf8, (erlang:element(9, Options))/binary>>/binary, " {\n"/utf8>> end, Graph_attrs = begin _pipe = [gleam@option:map( erlang:element(10, Options), fun(V) -> <<"layout="/utf8, (layout_to_string(V))/binary>> end ), gleam@option:map( erlang:element(11, Options), fun(V@1) -> <<"rankdir="/utf8, (rankdir_to_string(V@1))/binary>> end ), gleam@option:map( erlang:element(12, Options), fun(V@2) -> <<<<"bgcolor=\""/utf8, V@2/binary>>/binary, "\""/utf8>> end ), gleam@option:map( erlang:element(13, Options), fun(V@3) -> <<"splines="/utf8, (splines_to_string(V@3))/binary>> end ), gleam@option:map( erlang:element(14, Options), fun(V@4) -> <<"overlap="/utf8, (overlap_to_string(V@4))/binary>> end ), gleam@option:map( erlang:element(15, Options), fun(V@5) -> <<"nodesep="/utf8, (gleam_stdlib:float_to_string(V@5))/binary>> end ), gleam@option:map( erlang:element(16, Options), fun(V@6) -> <<"ranksep="/utf8, (gleam_stdlib:float_to_string(V@6))/binary>> end )], gleam@list:filter_map(_pipe, fun(Opt) -> case Opt of {some, Attr} -> {ok, Attr}; none -> {error, nil} end end) end, Graph_attr_line = case Graph_attrs of [] -> <<""/utf8>>; Attrs -> <<<<" graph ["/utf8, (gleam@string:join(Attrs, <<", "/utf8>>))/binary>>/binary, "];\n"/utf8>> end, Node_attrs = [<<"shape="/utf8, (node_shape_to_string(erlang:element(17, Options)))/binary>>, <<"style="/utf8, (style_to_string(erlang:element(19, Options)))/binary>>, <<<<"fillcolor=\""/utf8, (erlang:element(18, Options))/binary>>/binary, "\""/utf8>>, <<<<"fontname=\""/utf8, (erlang:element(20, Options))/binary>>/binary, "\""/utf8>>, <<"fontsize="/utf8, (erlang:integer_to_binary(erlang:element(21, Options)))/binary>>, <<<<"fontcolor=\""/utf8, (erlang:element(22, Options))/binary>>/binary, "\""/utf8>>], Base_node_style = <<<<" node ["/utf8, (gleam@string:join(Node_attrs, <<", "/utf8>>))/binary>>/binary, "];\n"/utf8>>, Edge_attrs = [<<<<"color=\""/utf8, (erlang:element(23, Options))/binary>>/binary, "\""/utf8>>, <<"style="/utf8, (style_to_string(erlang:element(24, Options)))/binary>>, <<<<"fontname=\""/utf8, (erlang:element(25, Options))/binary>>/binary, "\""/utf8>>, <<"fontsize="/utf8, (erlang:integer_to_binary(erlang:element(26, Options)))/binary>>, <<"penwidth="/utf8, (gleam_stdlib:float_to_string(erlang:element(27, Options)))/binary>>], Edge_attrs_with_arrows = case {erlang:element(28, Options), erlang:element(29, Options)} of {{some, Head}, {some, Tail}} -> [<<"arrowhead="/utf8, (arrow_style_to_string(Head))/binary>>, <<"arrowtail="/utf8, (arrow_style_to_string(Tail))/binary>> | Edge_attrs]; {{some, Head@1}, none} -> [<<"arrowhead="/utf8, (arrow_style_to_string(Head@1))/binary>> | Edge_attrs]; {none, {some, Tail@1}} -> [<<"arrowtail="/utf8, (arrow_style_to_string(Tail@1))/binary>> | Edge_attrs]; {none, none} -> Edge_attrs end, Base_edge_style = <<<<" edge ["/utf8, (gleam@string:join(Edge_attrs_with_arrows, <<", "/utf8>>))/binary>>/binary, "];\n"/utf8>>, Nodes = begin _pipe@1 = gleam@dict:fold( erlang:element(3, Graph), [], fun(Acc, Id, Data) -> Label = (erlang:element(2, Options))(Id, Data), Id_str = erlang:integer_to_binary(Id), Attrs@1 = [{<<"label"/utf8>>, Label}], Attrs@2 = case erlang:element(4, Options) of {some, Highlighted} -> case gleam@list:contains(Highlighted, Id) of true -> [{<<"fillcolor"/utf8>>, erlang:element(30, Options)} | Attrs@1]; false -> Attrs@1 end; none -> Attrs@1 end, Custom_attrs = (erlang:element(6, Options))(Id, Data), Attrs@3 = merge_attributes_list(Attrs@2, Custom_attrs), Attr_str = format_attributes_list(Attrs@3), [<<<<<<<<" "/utf8, Id_str/binary>>/binary, " ["/utf8>>/binary, Attr_str/binary>>/binary, "];"/utf8>> | Acc] end ), gleam@string:join(_pipe@1, <<"\n"/utf8>>) end, Subgraphs_str = case erlang:element(8, Options) of none -> <<""/utf8>>; {some, Subgraph_list} -> _pipe@4 = gleam@list:map( Subgraph_list, fun(Sub) -> Header = <<<<" subgraph "/utf8, (erlang:element(2, Sub))/binary>>/binary, " {\n"/utf8>>, Label@1 = case erlang:element(3, Sub) of {some, L} -> <<<<" label=\""/utf8, L/binary>>/binary, "\";\n"/utf8>>; none -> <<""/utf8>> end, Style = case erlang:element(5, Sub) of {some, S} -> <<<<" style="/utf8, (style_to_string(S))/binary>>/binary, ";\n"/utf8>>; none -> <<""/utf8>> end, Fillcolor = case erlang:element(6, Sub) of {some, F} -> <<<<" fillcolor=\""/utf8, F/binary>>/binary, "\";\n"/utf8>>; none -> <<""/utf8>> end, Color = case erlang:element(7, Sub) of {some, C} -> <<<<" color=\""/utf8, C/binary>>/binary, "\";\n"/utf8>>; none -> <<""/utf8>> end, Node_list = case erlang:element(4, Sub) of [] -> <<""/utf8>>; Ids -> <<(begin _pipe@2 = Ids, _pipe@3 = gleam@list:map( _pipe@2, fun(Id@1) -> <<" "/utf8, (erlang:integer_to_binary(Id@1))/binary>> end ), gleam@string:join(_pipe@3, <<";\n"/utf8>>) end)/binary, ";\n"/utf8>> end, <<<<<<<<<<<
>/binary, Style/binary>>/binary, Fillcolor/binary>>/binary, Color/binary>>/binary, Node_list/binary>>/binary, " }"/utf8>> end ), gleam@string:join(_pipe@4, <<"\n"/utf8>>) end, Edges = begin _pipe@5 = gleam@dict:fold( erlang:element(4, Graph), [], fun(Acc@1, From_id, Targets) -> Inner_edges = gleam@dict:fold( Targets, [], fun(Inner_acc, To_id, Weight) -> Is_valid = case erlang:element(2, Graph) of undirected -> From_id =< To_id; directed -> true end, case Is_valid of false -> Inner_acc; true -> Connector = case erlang:element(2, Graph) of directed -> <<" -> "/utf8>>; undirected -> <<" -- "/utf8>> end, Label@2 = (erlang:element(3, Options))(Weight), Attrs@4 = [{<<"label"/utf8>>, Label@2}], Is_highlighted = case erlang:element(5, Options) of {some, Highlighted@1} -> gleam@list:contains( Highlighted@1, {From_id, To_id} ) orelse gleam@list:contains( Highlighted@1, {To_id, From_id} ); none -> false end, Attrs@5 = case Is_highlighted of true -> [{<<"penwidth"/utf8>>, gleam_stdlib:float_to_string( erlang:element(31, Options) )}, {<<"color"/utf8>>, erlang:element(30, Options)} | Attrs@4]; false -> Attrs@4 end, Custom_attrs@1 = (erlang:element(7, Options))( From_id, To_id, Weight ), Attrs@6 = merge_attributes_list( Attrs@5, Custom_attrs@1 ), Attr_str@1 = format_attributes_list(Attrs@6), Edge_def = <<<<<<<<<<<<" "/utf8, (erlang:integer_to_binary( From_id ))/binary>>/binary, Connector/binary>>/binary, (erlang:integer_to_binary(To_id))/binary>>/binary, " ["/utf8>>/binary, Attr_str@1/binary>>/binary, "];"/utf8>>, [Edge_def | Inner_acc] end end ), lists:append([Inner_edges, Acc@1]) end ), gleam@string:join(_pipe@5, <<"\n"/utf8>>) end, <<<<<<<<<<<<<<<>/binary, Base_node_style/binary>>/binary, Base_edge_style/binary>>/binary, Nodes/binary>>/binary, "\n"/utf8>>/binary, (case Subgraphs_str of <<""/utf8>> -> <<""/utf8>>; S@1 -> <> end)/binary>>/binary, Edges/binary>>/binary, "\n}"/utf8>>.