diff options
| author | Marcos Magueta <maguetamarcos@gmail.com> | 2025-11-25 15:59:08 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-25 15:59:08 -0300 |
| commit | 3f7cb91ef7ae44d3f641f2e77826965fcd0afeed (patch) | |
| tree | adb493947fe7c3ee511346c984f8046b4c48856e /src/visualizer.erl | |
| parent | 62a017ce95ad2f6d9445b5b96f7aeba3f7c0d42f (diff) | |
| download | Sakura-3f7cb91ef7ae44d3f641f2e77826965fcd0afeed.tar.gz Sakura-3f7cb91ef7ae44d3f641f2e77826965fcd0afeed.zip | |
Pseudo-REPL and sketch planner (#12)
Diffstat (limited to '')
| -rw-r--r-- | src/visualizer.erl | 349 |
1 files changed, 349 insertions, 0 deletions
diff --git a/src/visualizer.erl b/src/visualizer.erl new file mode 100644 index 0000000..83080f1 --- /dev/null +++ b/src/visualizer.erl @@ -0,0 +1,349 @@ +%%% @doc Relation Visualizer - Multiple Rendering Modes +%%% +%%% Provides various visual representations for nested relational data. +%%% Supports different modes optimized for different use cases. +%%% +%%% == Modes == +%%% +%%% - `table` - Classic flat table (default) +%%% - `nested_table` - Tables within table cells +%%% - `tree` - Hierarchical tree view with indentation +%%% - `linked_tables` - Separate linked tables for nested relations +%%% - `outline` - Indented outline format +%%% +%%% == Usage == +%%% +%%% ``` +%%% Results = query_planner:collect(DB, Plan), +%%% visualizer:render(Results, tree). +%%% visualizer:render(Results, nested_table). +%%% ``` +%%% +%%% @author Nekoma Team +%%% @copyright 2025 + +-module(visualizer). + +-export([render/1, + render/2, + modes/0]). + +-type render_mode() :: table + | nested_table + | tree + | linked_tables + | outline. + +-export_type([render_mode/0]). + +%%% Public API + +%% @doc Render tuples with default mode (table). +-spec render([map()]) -> ok. +render(Tuples) -> + render(Tuples, table). + +%% @doc Render tuples with specified visualization mode. +-spec render([map()], render_mode()) -> ok. +render([], _Mode) -> + io:format("(no results)~n"), + ok; +render(Tuples, table) -> + render_table(Tuples); +render(Tuples, nested_table) -> + render_nested_table(Tuples); +render(Tuples, tree) -> + render_tree(Tuples); +render(Tuples, linked_tables) -> + render_linked_tables(Tuples); +render(Tuples, outline) -> + render_outline(Tuples). + +%% @doc List available rendering modes. +-spec modes() -> [render_mode()]. +modes() -> + [table, nested_table, tree, linked_tables, outline]. + +%%% Rendering Implementations + +%% Mode 1: Table with proper column width calculation +render_table(Tuples) -> + AllKeys = get_all_keys(Tuples), + + % Calculate column widths based on content + Widths = calculate_column_widths(Tuples, AllKeys), + + io:format("~n"), + print_separator_with_widths(Widths), + print_row_with_widths([atom_to_list(K) || K <- AllKeys], Widths), + print_separator_with_widths(Widths), + + lists:foreach( + fun(Tuple) -> + Values = [format_value_for_table(maps:get(K, Tuple, undefined)) || K <- AllKeys], + print_row_with_widths(Values, Widths) + end, + Tuples + ), + print_separator_with_widths(Widths), + io:format("~n~b tuple(s)~n~n", [length(Tuples)]), + ok. + +calculate_column_widths(Tuples, Keys) -> + lists:map( + fun(Key) -> + % Start with header width + HeaderWidth = length(atom_to_list(Key)), + + % Find max value width in this column + MaxValueWidth = lists:foldl( + fun(Tuple, MaxW) -> + Value = maps:get(Key, Tuple, undefined), + ValueStr = format_value_for_table(Value), + max(MaxW, length(ValueStr)) + end, + HeaderWidth, + Tuples + ), + + max(12, MaxValueWidth) % Minimum width of 12 + end, + Keys + ). + +format_value_for_table(V) when is_map(V) -> + % Format nested map as inline representation + Keys = [K || K <- maps:keys(V), K =/= meta], + Parts = [atom_to_list(K) ++ ":" ++ format_value(maps:get(K, V)) || K <- Keys], + "[" ++ string:join(Parts, ", ") ++ "]"; +format_value_for_table(V) -> + format_value(V). + +print_separator_with_widths(Widths) -> + io:format("+"), + lists:foreach( + fun(W) -> + io:format("~s+", [lists:duplicate(W + 2, $-)]) + end, + Widths + ), + io:format("~n"). + +print_row_with_widths(Values, Widths) -> + io:format("| "), + lists:foreach( + fun({Val, Width}) -> + io:format("~*s | ", [-Width, Val]) + end, + lists:zip(Values, Widths) + ), + io:format("~n"). + +%% Mode 2: Nested tables (tables within cells) +render_nested_table(Tuples) -> + io:format("~n=== Nested Table View ===~n~n"), + lists:foreach( + fun(Tuple) -> + render_tuple_nested(Tuple, 0) + end, + Tuples + ), + io:format("~n~b tuple(s)~n~n", [length(Tuples)]), + ok. + +render_tuple_nested(Tuple, Indent) -> + Keys = [K || K <- maps:keys(Tuple), K =/= meta], + IndentStr = lists:duplicate(Indent, $\s), + + io:format("~s+-- Tuple~n", [IndentStr]), + lists:foreach( + fun(Key) -> + Value = maps:get(Key, Tuple), + case is_map(Value) andalso (Key =/= meta) of + true -> + % Nested tuple - render as inner table + io:format("~s| ~s:~n", [IndentStr, atom_to_list(Key)]), + io:format("~s| +-------------------------+~n", [IndentStr]), + NestedKeys = maps:keys(Value), + lists:foreach( + fun(NK) -> + NV = format_value(maps:get(NK, Value)), + io:format("~s| | ~-10s : ~-10s |~n", + [IndentStr, atom_to_list(NK), NV]) + end, + NestedKeys + ), + io:format("~s| +-------------------------+~n", [IndentStr]); + false -> + % Simple value + io:format("~s| ~-15s : ~s~n", + [IndentStr, atom_to_list(Key), format_value(Value)]) + end + end, + Keys + ), + io:format("~s+--~n", [IndentStr]). + +%% Mode 3: Tree view with hierarchy +render_tree(Tuples) -> + io:format("~n=== Tree View ===~n~n"), + io:format("+-- Relation (~b tuples)~n", [length(Tuples)]), + render_tuples_as_tree(Tuples, "| ", length(Tuples)), + io:format("~n"), + ok. + +render_tuples_as_tree([], _Prefix, _Total) -> + ok; +render_tuples_as_tree([Tuple], Prefix, Total) -> + io:format("~s~n~s+-- Row ~b~n", [Prefix, Prefix, Total]), + render_tuple_as_tree(Tuple, Prefix ++ " "); +render_tuples_as_tree([Tuple | Rest], Prefix, Total) -> + Index = Total - length(Rest), + io:format("~s~n~s+-- Row ~b~n", [Prefix, Prefix, Index]), + render_tuple_as_tree(Tuple, Prefix ++ "| "), + render_tuples_as_tree(Rest, Prefix, Total). + +render_tuple_as_tree(Tuple, Prefix) -> + Keys = [K || K <- maps:keys(Tuple), K =/= meta], + render_attrs_as_tree(Keys, Tuple, Prefix). + +render_attrs_as_tree([], _Tuple, _Prefix) -> + ok; +render_attrs_as_tree([Key], Tuple, Prefix) -> + Value = maps:get(Key, Tuple), + case is_map(Value) andalso (Key =/= meta) of + true -> + io:format("~s+-- ~s:~n", [Prefix, atom_to_list(Key)]), + render_tuple_as_tree(Value, Prefix ++ " "); + false -> + io:format("~s+-- ~s: ~s~n", + [Prefix, atom_to_list(Key), format_value(Value)]) + end; +render_attrs_as_tree([Key | Rest], Tuple, Prefix) -> + Value = maps:get(Key, Tuple), + case is_map(Value) andalso (Key =/= meta) of + true -> + io:format("~s+-- ~s:~n", [Prefix, atom_to_list(Key)]), + render_tuple_as_tree(Value, Prefix ++ "| "), + render_attrs_as_tree(Rest, Tuple, Prefix); + false -> + io:format("~s+-- ~s: ~s~n", + [Prefix, atom_to_list(Key), format_value(Value)]), + render_attrs_as_tree(Rest, Tuple, Prefix) + end. + +%% Mode 4: Linked tables (parent + separate child tables) +render_linked_tables(Tuples) -> + io:format("~n=== Linked Tables View ===~n~n"), + + % Collect nested relations with parent references + {FlatTuples, NestedRels} = extract_nested_relations_with_refs(Tuples), + + % Render main table + io:format("Main Relation:~n"), + render_table(FlatTuples), + + % Render nested tables with parent references + case NestedRels of + [] -> ok; + _ -> + io:format("~nNested Relations:~n"), + lists:foreach( + fun({RowIdx, AttrName, NestedTuple}) -> + io:format("~n[~s for Row ~b]:~n", [atom_to_list(AttrName), RowIdx]), + render_table([NestedTuple]) + end, + NestedRels + ) + end, + ok. + +extract_nested_relations_with_refs(Tuples) -> + % Process tuples with index tracking + {FlatTuples, NestedRels} = lists:foldl( + fun(Tuple, {FlatAcc, NestedAcc}) -> + RowIdx = length(FlatAcc) + 1, + + % Flatten this tuple + FlatTuple = maps:map( + fun(K, V) when is_map(V), K =/= meta -> + lists:flatten(io_lib:format("[~b]", [RowIdx])); + (_K, V) -> V + end, + Tuple + ), + + % Extract nested relations with parent reference + Nested = lists:filtermap( + fun(K) -> + case maps:get(K, Tuple) of + V when is_map(V), K =/= meta -> + {true, {RowIdx, K, V}}; + _ -> + false + end + end, + maps:keys(Tuple) + ), + + {[FlatTuple | FlatAcc], Nested ++ NestedAcc} + end, + {[], []}, + Tuples + ), + + {lists:reverse(FlatTuples), lists:reverse(NestedRels)}. + +%% Mode 5: Outline format (indented text) +render_outline(Tuples) -> + io:format("~n=== Outline View ===~n~n"), + lists:foreach( + fun(Tuple) -> + render_tuple_outline(Tuple, 0) + end, + Tuples + ), + io:format("~n~b tuple(s)~n~n", [length(Tuples)]), + ok. + +render_tuple_outline(Tuple, Indent) -> + Keys = [K || K <- maps:keys(Tuple), K =/= meta], + IndentStr = lists:duplicate(Indent, $\s), + + io:format("~s+-- Tuple~n", [IndentStr]), + lists:foreach( + fun(Key) -> + Value = maps:get(Key, Tuple), + case is_map(Value) andalso (Key =/= meta) of + true -> + io:format("~s| ~s:~n", [IndentStr, atom_to_list(Key)]), + render_tuple_outline(Value, Indent + 4); + false -> + io:format("~s| ~s: ~s~n", + [IndentStr, atom_to_list(Key), format_value(Value)]) + end + end, + Keys + ), + io:format("~s+--~n", [IndentStr]). + +%%% Helper Functions + +get_all_keys(Tuples) -> + lists:usort( + lists:flatmap( + fun(Tuple) -> + [K || K <- maps:keys(Tuple), K =/= meta] + end, + Tuples + ) + ). + +format_value(undefined) -> "(null)"; +format_value(V) when is_integer(V) -> integer_to_list(V); +format_value(V) when is_float(V) -> float_to_list(V, [{decimals, 2}]); +format_value(V) when is_atom(V) -> atom_to_list(V); +format_value(V) when is_binary(V) -> binary_to_list(V); +format_value(V) when is_list(V) -> V; +format_value(V) when is_map(V) -> "(nested)"; +format_value(_) -> "(complex)". |
