Packages

ElementTuiComponents: TUI component companion library for ElementTui.

Current section

Files

Jump to
elementtui_components lib components xygraph.ex
Raw

lib/components/xygraph.ex

defmodule ElementTui.Components.XYGraph do
# TODO: Allow pushing extra x, y points?
alias ElementTui.Element
alias ElementTui.Components.XYGraph
defstruct bounds: nil,
data: nil,
convert_xy: nil,
to_label: nil
def new(data) do
state = %XYGraph{
data: data,
convert_xy: fn xy -> xy end,
to_label: fn {x, y} -> {"#{x}", "#{y}"} end
}
bounds(state)
end
def convert_xy(state, func) do
%XYGraph{state | convert_xy: func}
end
def to_label(state, func) do
%XYGraph{state | to_label: func}
end
def bounds(%XYGraph{data: []} = state) do
%XYGraph{state | bounds: {-1, 1, -1, 1}}
end
def bounds(%XYGraph{data: [{x, y} | xys]} = state) do
bounds =
xys
|> Enum.reduce({x, x, y, y}, fn {x, y}, {xmin, xmax, ymin, ymax} ->
{Cmp.min(xmin, x), Cmp.max(xmax, x), Cmp.min(ymin, y), Cmp.max(ymax, y)}
end)
%XYGraph{state | bounds: bounds}
end
def update_bounds(%XYGraph{bounds: bounds} = xygraph, func) do
%XYGraph{xygraph | bounds: func.(bounds)}
end
def render(
%XYGraph{
bounds: {xmin, xmax, ymin, ymax},
data: data,
convert_xy: convert_xy,
to_label: to_label
},
screenx,
screeny,
screenw,
screenh
) do
# x should be numeric
{xmin_f, ymin_f} = convert_xy.({xmin, ymin})
{xmax_f, ymax_f} = convert_xy.({xmax, ymax})
{dx, dy} = {xmax_f - xmin_f, ymax_f - ymin_f}
# Axis
{xlmin, ylmin} = to_label.({xmin, ymin})
{xlmax, ylmax} = to_label.({xmax, ymax})
{dx, xlmax} =
if dx == 0 do
# We have no way to know the new value, because xmax and xmax_f are not the same
{1, "N/A"}
else
{dx, xlmax}
end
{dy, ylmax} =
if dy == 0 do
{1, "N/A"}
else
{dy, ylmax}
end
margin_x = Cmp.max(String.length(ylmin), String.length(ylmax)) + 1
margin_y = 1
Element.text_line(ylmin)
|> ElementTui.render(screenx, screeny + screenh - margin_y, margin_x, 1)
Element.text_line(ylmax)
|> ElementTui.render(screenx, screeny, margin_x, 1)
Element.text_line(xlmin)
|> ElementTui.render(screenx + margin_x, screeny + screenh, screenw, 1)
ln = String.length(xlmax)
Element.text_line(xlmax)
|> ElementTui.render(screenx + screenw - ln, screeny + screenh, ln, 1)
# Data to screen location. The -1 is for corner cases to stop them falling of the edges
# Ideally add some tests around this.
{dxscreen, dyscreen} = {1.0 * (screenw - margin_x - 1), 1.0 * (screenh - margin_y)}
# Scale to screen x, y
data
|> Enum.map(fn xy ->
{x, y} = convert_xy.(xy)
{dxscreen * (x - xmin_f) / dx, dyscreen * (y - ymin_f) / dy}
end)
|> Enum.each(fn {x, y} ->
Element.text_line("x")
|> ElementTui.render(screenx + margin_x + floor(x), screeny + ceil(dyscreen - y), 1, 1)
end)
end
end