diff --git a/lib/matplotex.ex b/lib/matplotex.ex index 7d48036..7d68157 100644 --- a/lib/matplotex.ex +++ b/lib/matplotex.ex @@ -10,6 +10,14 @@ defmodule Matplotex do alias Matplotex.Figure alias Matplotex.Figure.Areal.BarChart + def bar(values, width) do + bar(width, values, width, []) + end + + def bar(values, width, opts) when is_list(opts) do + bar(width, values, width, opts) + end + def bar(pos, values, width) do bar(pos, values, width, []) end diff --git a/lib/matplotex/figure.ex b/lib/matplotex/figure.ex index b8d7129..892c629 100644 --- a/lib/matplotex/figure.ex +++ b/lib/matplotex/figure.ex @@ -3,6 +3,8 @@ defmodule Matplotex.Figure do @row_column_default 1 @margin_default 0.05 @figsize {10, 6} + + @restricted_keys [:axes, :element, :valid?, :rc_params, :errors] defstruct [ :id, :axes, @@ -32,6 +34,8 @@ defmodule Matplotex.Figure do struct(__MODULE__, opts) end + def restricted_keys(), do: @restricted_keys + # TODO: put error message in error # def put_error(figure, opts) do diff --git a/lib/matplotex/figure/areal/bar_chart.ex b/lib/matplotex/figure/areal/bar_chart.ex index 3932729..537e6f4 100644 --- a/lib/matplotex/figure/areal/bar_chart.ex +++ b/lib/matplotex/figure/areal/bar_chart.ex @@ -1,5 +1,6 @@ defmodule Matplotex.Figure.Areal.BarChart do import Matplotex.Figure.Numer + alias Matplotex.Figure.Areal.PlotOptions alias Matplotex.Figure.Areal.Region alias Matplotex.Element.Legend alias Matplotex.Figure.Dataset @@ -41,6 +42,7 @@ defmodule Matplotex.Figure.Areal.BarChart do | rc_params: %RcParams{white_space: width, y_padding: 0}, axes: %{axes | data: xydata, dataset: datasets} } + |> PlotOptions.set_options_in_figure(opts) end @impl Areal diff --git a/lib/matplotex/figure/areal/line_plot.ex b/lib/matplotex/figure/areal/line_plot.ex index 9412177..fd24fa4 100644 --- a/lib/matplotex/figure/areal/line_plot.ex +++ b/lib/matplotex/figure/areal/line_plot.ex @@ -1,4 +1,5 @@ defmodule Matplotex.Figure.Areal.LinePlot do + alias Matplotex.Figure.Areal.PlotOptions alias Matplotex.Utils.Algebra alias Matplotex.Figure.Areal.Region alias Matplotex.Figure.Areal.Ticker @@ -31,14 +32,16 @@ defmodule Matplotex.Figure.Areal.LinePlot do def create( %Figure{axes: %__MODULE__{dataset: data} = axes} = figure, {x, y}, - opts \\ [] + opts ) do x = determine_numeric_value(x) y = determine_numeric_value(y) dataset = Dataset.cast(%Dataset{x: x, y: y}, opts) datasets = data ++ [dataset] xydata = flatten_for_data(datasets) + %Figure{figure | axes: %{axes | data: xydata, dataset: datasets}} + |> PlotOptions.set_options_in_figure(opts) end @impl Areal diff --git a/lib/matplotex/figure/areal/plot_options.ex b/lib/matplotex/figure/areal/plot_options.ex index 77b14c7..ef6e3d1 100644 --- a/lib/matplotex/figure/areal/plot_options.ex +++ b/lib/matplotex/figure/areal/plot_options.ex @@ -99,6 +99,48 @@ defmodule Matplotex.Figure.Areal.PlotOptions do |> set_options_in_rc_params_struct(options) end + @spec set_options_in_figure(Figure.t(), keyword()) :: Figure.t() + def set_options_in_figure(%Figure{} = figure, opts) do + figure + |> cast_figure(opts) + |> cast_axes(opts) + |> cast_rc_params(opts) + end + + defp cast_figure(figure, opts) do + struct(figure, opts) + end + + defp cast_axes(%Figure{axes: axes} = figure, opts) do + opts = Keyword.delete(opts, :label) + + %Figure{ + figure + | axes: axes |> struct(opts) |> cast_two_d_structs(opts) + # |>fulfill_tick_and_lim() + } + end + + # defp fulfill_tick_and_lim(%{tick: nil, limit: nil} = axes) do + + # end + + defp cast_two_d_structs(%{label: label, tick: tick, limit: limit} = axes, opts) + when is_map(opts) do + %{ + axes + | label: TwoD.update(label, opts, :label), + tick: TwoD.update(tick, opts, :tick), + limit: TwoD.update(limit, opts, :limit) + } + end + + defp cast_two_d_structs(axes, opts), do: cast_two_d_structs(axes, Enum.into(opts, %{})) + + defp cast_rc_params(%Figure{rc_params: rc_params} = figure, opts) do + %Figure{figure | rc_params: rc_params |> RcParams.update_with_font(opts) |> struct(opts)} + end + defp set_options_in_figure_struct(%Figure{} = figure, %__MODULE__{ figsize: figsize, figrows: figrows, diff --git a/lib/matplotex/figure/areal/scatter.ex b/lib/matplotex/figure/areal/scatter.ex index c9d559d..ec6525c 100644 --- a/lib/matplotex/figure/areal/scatter.ex +++ b/lib/matplotex/figure/areal/scatter.ex @@ -1,4 +1,5 @@ defmodule Matplotex.Figure.Areal.Scatter do + alias Matplotex.Figure.Areal.PlotOptions alias Matplotex.Figure.Areal.Region alias Matplotex.Figure.Areal.Ticker alias Matplotex.Figure.Marker @@ -32,7 +33,9 @@ defmodule Matplotex.Figure.Areal.Scatter do dataset = Dataset.cast(%Dataset{x: x, y: y}, opts) datasets = data ++ [dataset] xydata = flatten_for_data(datasets) + %Figure{figure | axes: %{axes | data: xydata, dataset: datasets}} + |> PlotOptions.set_options_in_figure(opts) end @impl Areal diff --git a/lib/matplotex/figure/cast.ex b/lib/matplotex/figure/cast.ex index 54f3eb7..a6057f7 100644 --- a/lib/matplotex/figure/cast.ex +++ b/lib/matplotex/figure/cast.ex @@ -89,7 +89,6 @@ defmodule Matplotex.Figure.Cast do } = figure ) do # Convert to the svg plane - {left_x, bottom_y} = Algebra.flip_y_coordinate({content_x, content_y}) {right_x, top_y} = diff --git a/lib/matplotex/figure/lead.ex b/lib/matplotex/figure/lead.ex index 5fa0ec2..424067a 100644 --- a/lib/matplotex/figure/lead.ex +++ b/lib/matplotex/figure/lead.ex @@ -78,14 +78,29 @@ defmodule Matplotex.Figure.Lead do end defp maybe_generate_ticks(ticks, limit, data, number_of_ticks) do - if is_nil(ticks) || length(ticks) < 3 do - generate_ticks(limit, data, ceil(number_of_ticks)) - else - {ticks, limit} + cond do + is_nil(ticks) || length(ticks) < 3 -> + generate_ticks(limit, data, ceil(number_of_ticks)) + + is_nil(limit) -> + {ticks, generate_limit(data)} + + true -> + {ticks, limit} end end defp generate_ticks(nil, data, number_of_ticks) do + data + |> generate_limit() + |> generate_ticks(data, number_of_ticks) + end + + defp generate_ticks({lower_limit, upper_limit} = lim, _data, number_of_ticks) do + {lower_limit |> Nx.linspace(upper_limit, n: number_of_ticks) |> Nx.to_list(), lim} + end + + defp generate_limit(data) do {min, upper_limit} = Enum.min_max(data) lower_limit = @@ -95,11 +110,7 @@ defmodule Matplotex.Figure.Lead do 0 end - generate_ticks({lower_limit, upper_limit}, data, number_of_ticks) - end - - defp generate_ticks({lower_limit, upper_limit} = lim, _data, number_of_ticks) do - {lower_limit |> Nx.linspace(upper_limit, n: number_of_ticks) |> Nx.to_list(), lim} + {lower_limit, upper_limit} end defp set_region_xy( @@ -124,6 +135,7 @@ defmodule Matplotex.Figure.Lead do ) do space_for_ylabel = height_required_for_text(y_label_font, y_label) y_tick = Enum.max_by(y_ticks, &tick_length(&1)) + space_for_yticks = length_required_for_text(y_tick_font, y_tick) space_required_for_region_y = @@ -352,8 +364,11 @@ defmodule Matplotex.Figure.Lead do rotation: 0 }, text - ), - do: tick_length(text) * to_number(font_size) * (pt_to_inch_ratio / 2) + flate + ) do + text_size = tick_length(text) * to_number(font_size) * (pt_to_inch_ratio / 2) + offset_for_text_length = 1 / tick_length(text) * (pt_to_inch_ratio / 2) * to_number(font_size) + text_size + offset_for_text_length + flate + end def length_required_for_text( %Font{ diff --git a/lib/matplotex/figure/rc_params.ex b/lib/matplotex/figure/rc_params.ex index 5308a80..2c7291a 100644 --- a/lib/matplotex/figure/rc_params.ex +++ b/lib/matplotex/figure/rc_params.ex @@ -128,7 +128,7 @@ defmodule Matplotex.Figure.RcParams do y_label_font end - def update_with_font(rc_params, params) do + def update_with_font(rc_params, params) when is_map(params) do rc_params |> update_font(params, :x_label) |> update_font(params, :y_label) @@ -137,6 +137,11 @@ defmodule Matplotex.Figure.RcParams do |> update_font(params, :title) end + def update_with_font(rc_params, params) do + params = Enum.into(params, %{}) + update_with_font(rc_params, params) + end + defp update_font( rc_params, params, diff --git a/lib/matplotex/figure/two_d.ex b/lib/matplotex/figure/two_d.ex index 4429a2b..74b2496 100644 --- a/lib/matplotex/figure/two_d.ex +++ b/lib/matplotex/figure/two_d.ex @@ -1,3 +1,15 @@ defmodule Matplotex.Figure.TwoD do defstruct [:x, :y] + + def update(twod, opts, context) do + %__MODULE__{ + twod + | x: fetch_from_opts(opts, :x, context), + y: fetch_from_opts(opts, :y, context) + } + end + + defp fetch_from_opts(opts, key, context) do + Map.get(opts, :"#{key}_#{context}") + end end diff --git a/lib/matplotex/helpers.ex b/lib/matplotex/helpers.ex index a9e2784..b08dd0b 100644 --- a/lib/matplotex/helpers.ex +++ b/lib/matplotex/helpers.ex @@ -18,44 +18,6 @@ defmodule Matplotex.Helpers do :ok end - def pie_chart_params() do - %{ - "id" => "chart-container", - "dataset" => [280, 45, 133, 152, 278, 221, 56], - "labels" => ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], - "color_palette" => ["#f66", "pink", "orange", "gray", "#fcc", "green", "#0f0"], - "width" => 700, - "height" => 400, - "margin" => 15, - "legends" => true - } - end - - def lineplot_params() do - %{ - "id" => "line-plot", - "dataset" => [ - [1, 9, 8, 4, 6, 5, 3], - [1, 6, 5, 3, 3, 8, 6] - ], - "x_labels" => ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], - "y_labels" => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - "width" => 700, - "height" => 400, - "x_margin" => 20, - "y_margin" => 10, - "x_label_offset" => 40, - "y_label_offset" => 40, - "y_scale" => 1, - "x_scale" => 1, - "color_palette" => ["red", "green"], - "type" => "line_chart", - "x_label" => "Days", - "y_label" => "Count", - "line_width" => 3 - } - end - def line_plot() do x = [1, 2, 3, 4, 6, 6, 7] y = [1, 3, 4, 4, 5, 6, 7] @@ -91,6 +53,40 @@ defmodule Matplotex.Helpers do |> copy() end + def line_plot_by_options() do + x = [1, 2, 3, 4, 6, 6, 7] + y = [1, 3, 4, 4, 5, 6, 7] + + frame_width = 6 + frame_height = 6 + size = {frame_width, frame_height} + margin = 0.05 + font_size = "16pt" + title_font_size = "18pt" + ticks = [0, 1, 2, 3, 4, 5, 6, 7] + + x + |> Matplotex.plot(y, + figsize: size, + margin: margin, + title: "The plot title", + x_label: "X Axis", + y_label: "Y Axis", + x_tick: ticks, + y_tick: ticks, + x_limit: {0, 7}, + y_limit: {0, 7}, + x_tick_font_size: font_size, + y_tick_font_size: font_size, + title_font_size: title_font_size, + x_label_font_size: font_size, + y_label_font_size: font_size, + y_tick_font_text_anchor: "start" + ) + |> Matplotex.show() + |> copy() + end + def line_plotc() do x = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] y = [1, 3, 4, 4, 5, 6, 7] diff --git a/test/matplotex_test.exs b/test/matplotex_test.exs index 999671f..fbd051e 100644 --- a/test/matplotex_test.exs +++ b/test/matplotex_test.exs @@ -145,4 +145,131 @@ defmodule MatplotexTest do assert height == fheight - fheight * margin * 2 end end + + describe "with plot options" do + test "updates the values according to the plot options in line_plot" do + x = [1, 3, 7, 4, 2, 5, 6] + y = [1, 3, 7, 4, 2, 5, 6] + + options = [ + x_label: "X Axis", + y_label: "Y Axis", + title: "The Plot Title", + x_ticks: [1, 2, 3, 4, 5, 6, 7], + y_ticks: [1, 2, 3, 4, 5, 6, 7], + x_tick_font_size: 12, + y_tick_font_size: 16, + title_font_size: 14, + x_label_font_size: 10, + y_label_font_size: 10, + title_font_size: 14, + line_width: 2, + figsize: {10, 10}, + x_limit: [1, 7], + y_limit: [1, 7], + line_style: "_" + ] + + figure = Matplotex.plot(x, y, options) + + assert figure.axes.label.x == "X Axis" + assert figure.axes.label.y == "Y Axis" + assert figure.axes.title == "The Plot Title" + assert figure.axes.dataset |> List.first() |> Map.get(:x) == x + assert figure.axes.dataset |> List.first() |> Map.get(:y) == y + assert figure.axes.limit.x == [1, 7] + assert figure.axes.limit.y == [1, 7] + assert figure.rc_params.line_width == 2 + assert figure.figsize == {10, 10} + assert figure.rc_params.line_style == "_" + assert figure.rc_params.x_tick_font.font_size == 12 + assert figure.rc_params.y_tick_font.font_size == 16 + assert figure.rc_params.title_font.font_size == 14 + assert figure.rc_params.x_label_font.font_size == 10 + assert figure.rc_params.y_label_font.font_size == 10 + end + end + + test "updates the values according to the plot options in bar_chart" do + values = [1, 3, 7, 4, 2, 5, 6] + width = 0.22 + + options = [ + x_label: "X Axis", + y_label: "Y Axis", + title: "The Plot Title", + x_ticks: [1, 2, 3, 4, 5, 6, 7], + y_ticks: [1, 2, 3, 4, 5, 6, 7], + x_tick_font_size: 12, + y_tick_font_size: 16, + title_font_size: 14, + x_label_font_size: 10, + y_label_font_size: 10, + title_font_size: 14, + line_width: 2, + figsize: {10, 10}, + x_limit: [1, 7], + y_limit: [1, 7], + line_style: "_" + ] + + figure = Matplotex.bar(values, width, options) + + assert figure.axes.label.x == "X Axis" + assert figure.axes.label.y == "Y Axis" + assert figure.axes.title == "The Plot Title" + assert figure.axes.dataset |> List.first() |> Map.get(:y) == values + assert figure.axes.limit.x == [1, 7] + assert figure.axes.limit.y == [1, 7] + assert figure.rc_params.line_width == 2 + assert figure.figsize == {10, 10} + assert figure.rc_params.line_style == "_" + assert figure.rc_params.x_tick_font.font_size == 12 + assert figure.rc_params.y_tick_font.font_size == 16 + assert figure.rc_params.title_font.font_size == 14 + assert figure.rc_params.x_label_font.font_size == 10 + assert figure.rc_params.y_label_font.font_size == 10 + end + + test "updates the values according to the plot options in scatter plot" do + x = [1, 3, 7, 4, 2, 5, 6] + y = [1, 3, 7, 4, 2, 5, 6] + + options = [ + x_label: "X Axis", + y_label: "Y Axis", + title: "The Plot Title", + x_ticks: [1, 2, 3, 4, 5, 6, 7], + y_ticks: [1, 2, 3, 4, 5, 6, 7], + x_tick_font_size: 12, + y_tick_font_size: 16, + title_font_size: 14, + x_label_font_size: 10, + y_label_font_size: 10, + title_font_size: 14, + line_width: 2, + figsize: {10, 10}, + x_limit: [1, 7], + y_limit: [1, 7], + line_style: "_" + ] + + figure = Matplotex.scatter(x, y, options) + + assert figure.axes.label.x == "X Axis" + assert figure.axes.label.y == "Y Axis" + assert figure.axes.title == "The Plot Title" + assert figure.axes.dataset |> List.first() |> Map.get(:x) == x + assert figure.axes.dataset |> List.first() |> Map.get(:y) == y + assert figure.axes.limit.x == [1, 7] + assert figure.axes.limit.y == [1, 7] + assert figure.rc_params.line_width == 2 + assert figure.figsize == {10, 10} + assert figure.rc_params.line_style == "_" + assert figure.rc_params.x_tick_font.font_size == 12 + assert figure.rc_params.y_tick_font.font_size == 16 + assert figure.rc_params.title_font.font_size == 14 + assert figure.rc_params.x_label_font.font_size == 10 + assert figure.rc_params.y_label_font.font_size == 10 + end end