TermUI.Widgets.VisualizationHelper (TermUI v0.2.0)
View SourceShared utilities for visualization widgets (charts, gauges, sparklines).
Provides common functions for:
- Value normalization and scaling
- Number formatting
- Color/zone threshold mapping
- Min/max range calculation
- Input validation
- Style application
Usage
alias TermUI.Widgets.VisualizationHelper, as: VizHelper
# Normalize a value to 0-1 range
VizHelper.normalize(75, 0, 100)
#=> 0.75
# Format numbers for display
VizHelper.format_number(3.14159)
#=> "3.1"
# Find style based on threshold zones
zones = [{0, :green}, {60, :yellow}, {80, :red}]
VizHelper.find_zone(85, zones)
#=> :red
Summary
Functions
Calculates min/max range from data, with optional overrides.
Clamps height to safe bounds.
Clamps width to safe bounds.
Gets a color from a list by cycling through indices.
Finds the appropriate style/color for a value based on threshold zones.
Formats a numeric value for display.
Returns the maximum allowed height for visualization widgets.
Returns the maximum allowed width for visualization widgets.
Applies style conditionally to a render node.
Normalizes a value to 0-1 range based on min/max bounds. Clamps result to [0, 1].
Normalizes and scales a value in one step.
Safely duplicates a string with bounds checking.
Scales a normalized value (0-1) to a target size.
Validates bar chart data structure.
Validates that a character is a single printable character.
Validates that a value is a number.
Validates that all values in a list are numbers.
Validates line chart series data structure.
Functions
Calculates min/max range from data, with optional overrides.
Examples
iex> VisualizationHelper.calculate_range([1, 5, 3, 9, 2])
{1, 9}
iex> VisualizationHelper.calculate_range([1, 5, 3], min: 0)
{0, 5}
iex> VisualizationHelper.calculate_range([1, 5, 3], min: 0, max: 10)
{0, 10}
iex> VisualizationHelper.calculate_range([])
{0, 1}
@spec clamp_height(integer()) :: pos_integer()
Clamps height to safe bounds.
Examples
iex> VisualizationHelper.clamp_height(20)
20
iex> VisualizationHelper.clamp_height(1000)
500
iex> VisualizationHelper.clamp_height(-5)
1
@spec clamp_width(integer()) :: pos_integer()
Clamps width to safe bounds.
Examples
iex> VisualizationHelper.clamp_width(50)
50
iex> VisualizationHelper.clamp_width(2000)
1000
iex> VisualizationHelper.clamp_width(-5)
1
@spec cycle_color([any()], non_neg_integer()) :: any() | nil
Gets a color from a list by cycling through indices.
Examples
iex> colors = [:red, :blue, :green]
iex> VisualizationHelper.cycle_color(colors, 0)
:red
iex> colors = [:red, :blue, :green]
iex> VisualizationHelper.cycle_color(colors, 4)
:blue
iex> VisualizationHelper.cycle_color([], 0)
nil
Finds the appropriate style/color for a value based on threshold zones.
Zones should be a list of {threshold, style} tuples. The function returns
the style associated with the highest threshold that is <= the value.
Examples
iex> zones = [{0, :green}, {60, :yellow}, {80, :red}]
iex> VisualizationHelper.find_zone(50, zones)
:green
iex> zones = [{0, :green}, {60, :yellow}, {80, :red}]
iex> VisualizationHelper.find_zone(75, zones)
:yellow
iex> zones = [{0, :green}, {60, :yellow}, {80, :red}]
iex> VisualizationHelper.find_zone(90, zones)
:red
iex> VisualizationHelper.find_zone(50, [])
nil
Formats a numeric value for display.
- Floats are formatted to 1 decimal place
- Integers are converted to string
- Other values return "???"
Examples
iex> VisualizationHelper.format_number(42)
"42"
iex> VisualizationHelper.format_number(3.14159)
"3.1"
iex> VisualizationHelper.format_number(:not_a_number)
"???"
@spec max_height() :: pos_integer()
Returns the maximum allowed height for visualization widgets.
@spec max_width() :: pos_integer()
Returns the maximum allowed width for visualization widgets.
Applies style conditionally to a render node.
Returns the node unchanged if style is nil.
Examples
iex> node = %{type: :text, content: "hello"}
iex> VisualizationHelper.maybe_style(node, nil)
%{type: :text, content: "hello"}
Normalizes a value to 0-1 range based on min/max bounds. Clamps result to [0, 1].
Returns 0.5 when min equals max to avoid division by zero.
Examples
iex> VisualizationHelper.normalize(50, 0, 100)
0.5
iex> VisualizationHelper.normalize(75, 0, 100)
0.75
iex> VisualizationHelper.normalize(150, 0, 100)
1.0
iex> VisualizationHelper.normalize(-10, 0, 100)
0.0
iex> VisualizationHelper.normalize(50, 50, 50)
0.5
Normalizes and scales a value in one step.
Examples
iex> VisualizationHelper.normalize_and_scale(50, 0, 100, 20)
10
iex> VisualizationHelper.normalize_and_scale(75, 0, 100, 40)
30
Safely duplicates a string with bounds checking.
Prevents memory exhaustion by clamping count to reasonable bounds.
Examples
iex> VisualizationHelper.safe_duplicate("█", 5)
"█████"
iex> VisualizationHelper.safe_duplicate("█", -5)
""
iex> VisualizationHelper.safe_duplicate("█", 10000)
# Returns string with max_width characters
Scales a normalized value (0-1) to a target size.
Examples
iex> VisualizationHelper.scale(0.5, 100)
50
iex> VisualizationHelper.scale(0.75, 20)
15
Validates bar chart data structure.
Each item must be a map with :label (string) and :value (number) keys.
Examples
iex> data = [%{label: "A", value: 10}, %{label: "B", value: 20}]
iex> VisualizationHelper.validate_bar_data(data)
:ok
iex> VisualizationHelper.validate_bar_data([%{label: "A"}])
{:error, "bar data item at index 0 missing :value key"}
iex> VisualizationHelper.validate_bar_data("not a list")
{:error, "expected a list of bar data items"}
Validates that a character is a single printable character.
Examples
iex> VisualizationHelper.validate_char("█")
:ok
iex> VisualizationHelper.validate_char("ab")
{:error, "expected a single character, got 2 characters"}
iex> VisualizationHelper.validate_char("")
{:error, "expected a single character, got empty string"}
Validates that a value is a number.
Examples
iex> VisualizationHelper.validate_number(42)
:ok
iex> VisualizationHelper.validate_number(3.14)
:ok
iex> VisualizationHelper.validate_number("not a number")
{:error, "expected a number, got: \"not a number\""}
Validates that all values in a list are numbers.
Examples
iex> VisualizationHelper.validate_number_list([1, 2, 3])
:ok
iex> VisualizationHelper.validate_number_list([1, "two", 3])
{:error, "all values must be numbers, found non-number at index 1"}
iex> VisualizationHelper.validate_number_list("not a list")
{:error, "expected a list of numbers"}
Validates line chart series data structure.
Each series must be a map with :data (list of numbers) and optional :color keys.
Examples
iex> series = [%{data: [1, 2, 3]}, %{data: [4, 5, 6], color: :red}]
iex> VisualizationHelper.validate_series_data(series)
:ok
iex> VisualizationHelper.validate_series_data([%{data: "not a list"}])
{:error, "series at index 0 :data must be a list of numbers"}