diff --git a/lib/chart/lineplot.ex b/lib/chart/lineplot.ex index 48e6b3e..824fae3 100644 --- a/lib/chart/lineplot.ex +++ b/lib/chart/lineplot.ex @@ -51,7 +51,6 @@ defmodule Contex.LinePlot do custom_y_formatter: nil, width: 100, height: 100, - smoothed: true, stroke_width: "2", colour_palette: :default ] @@ -106,7 +105,9 @@ defmodule Contex.LinePlot do - `:stroke_width` : 2 (default) - stroke width of the line - - `:smoothed` : true (default) or false - draw the lines smoothed + - `:plot_style` : :direct (default), :smooth or :step + + For backwards compatibility, the option :smoothed was kept and is translated: true -> :smooth and false -> :direct. Note that the smoothing algorithm is a cardinal spline with tension = 0.3. You may get strange effects (e.g. loops / backtracks) in certain circumstances, e.g. @@ -254,7 +255,17 @@ defmodule Contex.LinePlot do y_accessor, colour ) do - smooth = get_option(plot, :smoothed) + # keep backwards compatibility with old `:smoothed` option + style = get_option(plot, :plot_style) + smoothed = get_option(plot, :smoothed) + + plot_style = + case smoothed do + true -> :smooth + false -> :direct + _ -> style + end + stroke_width = get_option(plot, :stroke_width) options = [ @@ -282,7 +293,7 @@ defmodule Contex.LinePlot do |> Enum.chunk_by(fn {_x, y} -> is_nil(y) end) |> Enum.filter(fn [{_x, y} | _] -> not is_nil(y) end) - Enum.map(points_list, fn points -> line(points, smooth, options) end) + Enum.map(points_list, fn points -> line(points, plot_style, options) end) end @doc false diff --git a/lib/chart/svg.ex b/lib/chart/svg.ex index 6547df3..0db1427 100644 --- a/lib/chart/svg.ex +++ b/lib/chart/svg.ex @@ -69,10 +69,10 @@ defmodule Contex.SVG do ] end - def line(points, smoothed, opts \\ []) do + def line(points, plot_style, opts \\ []) do attrs = opts_to_attrs(opts) - path = path(points, smoothed) + path = path(points, plot_style) [ " coord = ~s|#{x} #{y}| @@ -96,7 +96,24 @@ defmodule Contex.SVG do end) end - defp path(points, true) do + defp path(points, :step) do + Enum.reduce(points, :first, fn {x, y}, acc -> + coord = ~s|#{x} #{y}| + + case acc do + :first -> + ["M ", coord] + + _ -> + previous_coord = acc |> List.last() + previous_y = previous_coord |> String.split(" ") |> List.last() + new_x = x + acc ++ [" L ", ~s|#{new_x} #{previous_y}|, " L ", coord] + end + end) + end + + defp path(points, :smooth) do # Use Catmull-Rom curve - see http://schepers.cc/getting-to-the-point # First point stays as-is. Subsequent points are draw using SVG cubic-spline # where control points are calculated as follows: diff --git a/test/contex_line_chart_test.exs b/test/contex_line_chart_test.exs index 6751848..9812941 100644 --- a/test/contex_line_chart_test.exs +++ b/test/contex_line_chart_test.exs @@ -41,8 +41,12 @@ defmodule ContexLineChartTest do C330.6666666666667,237.5 388.3333333333333,60 404,20 " """ - output = Contex.SVG.line(points, true) |> IO.iodata_to_binary() - IO.inspect(output) + output_new = Contex.SVG.line(points, :smooth) |> IO.iodata_to_binary() + output_old = Contex.SVG.line(points, true) |> IO.iodata_to_binary() + + assert output_new == output_old + + IO.inspect(output_new) end end end