Skip to content

Commit

Permalink
Add support for providing floating-point value precision
Browse files Browse the repository at this point in the history
  • Loading branch information
sndnv committed Dec 17, 2023
1 parent b715d1a commit 167b95b
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 14 deletions.
40 changes: 26 additions & 14 deletions lib/asciichart.ex
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ defmodule Asciichart do
* :height - adjusts the height of the chart
* :padding - one or more characters to use for the label's padding (left)
* :charset - a customizable character set
* :precision - number of fractional digits to keep for floating-point values
## Examples
iex> Asciichart.plot([1, 2, 3, 3, 2, 1])
Expand Down Expand Up @@ -94,6 +95,15 @@ defmodule Asciichart do
_50.00 ┤ ││
__0.00 ┼──────╯╰
iex> Asciichart.plot([1, 2, 5, 5, 4, 3, 2, 100, 0], height: 3, offset: 10, padding: "__", precision: 3)
{:ok, " 100.000 ┤ ╭╮ \n _50.000 ┤ ││ \n __0.000 ┼──────╯╰ \n "}
# should render as
100.000 ┤ ╭╮
_50.000 ┤ ││
__0.000 ┼──────╯╰
# Rendering of empty charts is not supported
Expand All @@ -108,15 +118,15 @@ defmodule Asciichart do
{:error, "No data"}

[_ | _] ->
cs = if cfg[:charset], do: cfg[:charset], else: Asciichart.Charset.round()

minimum = Enum.min(series)
maximum = Enum.max(series)

interval = abs(maximum - minimum)
offset = cfg[:offset] || 3
height = if cfg[:height], do: cfg[:height] - 1, else: interval
padding = cfg[:padding] || " "
charset = if cfg[:charset], do: cfg[:charset], else: Asciichart.Charset.round()
precision = cfg[:precision] || 2
ratio = height / interval
min2 = Float.floor(minimum * ratio)
max2 = Float.ceil(maximum * ratio)
Expand All @@ -137,15 +147,17 @@ defmodule Asciichart do

max_label_size =
(maximum / 1)
|> Float.round(2)
|> :erlang.float_to_binary(decimals: 2)
|> Float.round(precision)
|> :erlang.float_to_binary(decimals: precision)
|> String.length()
|> max(3)

min_label_size =
(minimum / 1)
|> Float.round(2)
|> :erlang.float_to_binary(decimals: 2)
|> Float.round(precision)
|> :erlang.float_to_binary(decimals: precision)
|> String.length()
|> max(3)

label_size = max(min_label_size, max_label_size)

Expand All @@ -155,17 +167,17 @@ defmodule Asciichart do
|> Enum.reduce(result, fn y, map ->
label =
(maximum - (y - intmin2) * interval / rows)
|> Float.round(2)
|> :erlang.float_to_binary(decimals: 2)
|> Float.round(precision)
|> :erlang.float_to_binary(decimals: precision)
|> String.pad_leading(label_size, padding)

updated_map = put_in(map[y - intmin2][max(offset - String.length(label), 0)], label)
put_in(updated_map[y - intmin2][offset - 1], cs.axis)
put_in(updated_map[y - intmin2][offset - 1], charset.axis)
end)

# first value
y0 = trunc(Enum.at(series, 0) * ratio - min2)
result = put_in(result[rows - y0][offset - 1], cs.firstval)
result = put_in(result[rows - y0][offset - 1], charset.firstval)

# plot the line
result =
Expand All @@ -175,24 +187,24 @@ defmodule Asciichart do
y1 = trunc(Enum.at(series, x + 1) * ratio - intmin2)

if y0 == y1 do
put_in(map[rows - y0][x + offset], cs.dash)
put_in(map[rows - y0][x + offset], charset.dash)
else
updated_map =
put_in(
map[rows - y1][x + offset],
if(y0 > y1, do: cs.bottomleft, else: cs.topleft)
if(y0 > y1, do: charset.bottomleft, else: charset.topleft)
)

updated_map =
put_in(
updated_map[rows - y0][x + offset],
if(y0 > y1, do: cs.topright, else: cs.bottomright)
if(y0 > y1, do: charset.topright, else: charset.bottomright)
)

(min(y0, y1) + 1)..max(y0, y1)
|> Enum.drop(-1)
|> Enum.reduce(updated_map, fn y, map ->
put_in(map[rows - y][x + offset], cs.pipe)
put_in(map[rows - y][x + offset], charset.pipe)
end)
end
end)
Expand Down
49 changes: 49 additions & 0 deletions test/asciichart_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule AsciichartTest do
test "renders charts" do
series = (1..6 |> Enum.to_list()) ++ (6..1 |> Enum.to_list())

# default config
expected_chart =
[
"6.00 ┤ ╭─╮ ",
Expand All @@ -23,6 +24,54 @@ defmodule AsciichartTest do
{:ok, actual_chart} = Asciichart.plot(series)
assert expected_chart == actual_chart

# with precision=3
expected_chart =
[
"6.000 ┤ ╭─╮ ",
"5.000 ┤ ╭╯ ╰╮ ",
"4.000 ┤ ╭╯ ╰╮ ",
"3.000 ┤ ╭╯ ╰╮ ",
"2.000 ┤╭╯ ╰╮ ",
"1.000 ┼╯ ╰ ",
" "
]
|> Enum.join("\n")

{:ok, actual_chart} = Asciichart.plot(series, precision: 3)
assert expected_chart == actual_chart

# with precision=0
expected_chart =
[
" 6 ┤ ╭─╮ ",
" 5 ┤ ╭╯ ╰╮ ",
" 4 ┤ ╭╯ ╰╮ ",
" 3 ┤ ╭╯ ╰╮ ",
" 2 ┤╭╯ ╰╮ ",
" 1 ┼╯ ╰ ",
" "
]
|> Enum.join("\n")

{:ok, actual_chart} = Asciichart.plot(series, precision: 0)
assert expected_chart == actual_chart

# with precision=1
expected_chart =
[
"6.0 ┤ ╭─╮ ",
"5.0 ┤ ╭╯ ╰╮ ",
"4.0 ┤ ╭╯ ╰╮ ",
"3.0 ┤ ╭╯ ╰╮ ",
"2.0 ┤╭╯ ╰╮ ",
"1.0 ┼╯ ╰ ",
" "
]
|> Enum.join("\n")

{:ok, actual_chart} = Asciichart.plot(series, precision: 1)
assert expected_chart == actual_chart

series =
[-8_975_789_655_001, 6_755_678_990_773]
|> Stream.cycle()
Expand Down

0 comments on commit 167b95b

Please sign in to comment.