diff --git a/lib/matplotex/blueprint/frame.ex b/lib/matplotex/blueprint/frame.ex index 5802db3..5b7e356 100644 --- a/lib/matplotex/blueprint/frame.ex +++ b/lib/matplotex/blueprint/frame.ex @@ -94,7 +94,8 @@ defmodule Matplotex.Blueprint.Frame do show_h_grid: @show_by_default, show_x_ticks: @show_by_default, show_y_ticks: @show_by_default, - show_ticks: @show_by_default + show_ticks: @show_by_default, + border: nil ] |> Keyword.merge(opts) ) diff --git a/lib/matplotex/element/label.ex b/lib/matplotex/element/label.ex index 361ae57..c2c1aef 100644 --- a/lib/matplotex/element/label.ex +++ b/lib/matplotex/element/label.ex @@ -57,6 +57,11 @@ defmodule Matplotex.Element.Label do """ end + def cast_label(label, font) do + font = Map.from_struct(font) + struct(label, font) + end + defp rotate(%{rotate: nil}), do: nil defp rotate(%{rotate: rotate, x: x, y: y}), diff --git a/lib/matplotex/figure/areal.ex b/lib/matplotex/figure/areal.ex index 72db769..1aadc7b 100644 --- a/lib/matplotex/figure/areal.ex +++ b/lib/matplotex/figure/areal.ex @@ -1,4 +1,5 @@ defmodule Matplotex.Figure.Areal do + alias Matplotex.Utils.Algebra alias Matplotex.Figure.Dataset alias Matplotex.Figure.TwoD @callback create(struct(), any(), keyword()) :: struct() @@ -29,7 +30,7 @@ defmodule Matplotex.Figure.Areal do alias Matplotex.Figure.Dataset alias Matplotex.Figure.Text - + @default_tick_minimum 0 def add_label(%__MODULE__{label: nil} = axes, {key, label}, opts) when is_binary(label) do label = Map.new() @@ -61,15 +62,18 @@ defmodule Matplotex.Figure.Areal do end def add_ticks(%__MODULE__{tick: tick} = axes, {key, ticks}) when is_list(ticks) do - ticks = + {ticks, lim} = if number_based?(ticks) do - ticks + {ticks, Enum.min_max(ticks)} else - Enum.with_index(ticks, 1) + {Enum.with_index(ticks), {@default_tick_minimum, length(ticks)}} end tick = Map.put(tick, key, ticks) - update_tick(axes, tick) + + axes + |> set_limit({key, lim}) + |> update_tick(tick) end def add_ticks(%__MODULE__{tick: tick, size: size} = axes, {key, {_min, _max} = lim}) do @@ -78,18 +82,18 @@ defmodule Matplotex.Figure.Areal do tick = Map.put(tick, key, ticks) axes - |> set_limit(lim) + |> set_limit({key, lim}) |> update_tick(tick) end - def hide_v_grid(axes) do - %{axes | show_v_grid: false} - end - def add_ticks(_, _) do raise Matplotex.InputError, keys: [:tick], message: "Invalid Input" end + def hide_v_grid(axes) do + %{axes | show_v_grid: false} + end + def set_limit(%__MODULE__{limit: limit} = axes, {key, {_, _} = lim}) do limit = Map.put(limit, key, lim) update_limit(axes, limit) @@ -137,16 +141,16 @@ defmodule Matplotex.Figure.Areal do defp update_limit(limit, _, _), do: limit - def materialized(figure) do + def materialized_by_region(figure) do figure - |> Lead.set_spines() - |> Cast.cast_xticks() - |> Cast.cast_yticks() - |> Cast.cast_hgrids() - |> Cast.cast_vgrids() - |> Cast.cast_spines() - |> Cast.cast_label() - |> Cast.cast_title() + |> Lead.set_regions() + |> Cast.cast_xticks_by_region() + |> Cast.cast_yticks_by_region() + |> Cast.cast_hgrids_by_region() + |> Cast.cast_vgrids_by_region() + |> Cast.cast_spines_by_region() + |> Cast.cast_label_by_region() + |> Cast.cast_title_by_region() end defp update_tick(axes, tick) do @@ -209,19 +213,18 @@ defmodule Matplotex.Figure.Areal do end end end - - @tensor_data_type_bits 64 - - alias Nx - - def transformation({_label, value}, y, xminmax, yminmax, width, height, transition) do - transformation(value, y, xminmax, yminmax, width, height, transition) + def transformation({_labelx, x}, {_labely, y}, xminmax, yminmax, width, height, transition) do + transformation(x, y, xminmax, yminmax, width, height, transition) + end + def transformation({_label, x}, y, xminmax, yminmax, width, height, transition) do + transformation(x, y, xminmax, yminmax, width, height, transition) end - def transformation(x, {_label, value}, y, xminmax, yminmax, width, transition) do - transformation(x, value, y, xminmax, yminmax, width, transition) + def transformation(x, {_label, y}, xminmax, yminmax, width, height, transition) do + transformation(x, y, xminmax, yminmax, width, height, transition) end + def transformation( x, y, @@ -233,24 +236,9 @@ defmodule Matplotex.Figure.Areal do ) do sx = svg_width / (xmax - xmin) sy = svg_height / (ymax - ymin) - tx = transition_x - xmin * sx ty = transition_y - ymin * sy - - # TODO: work for the datasets which has values in a range way far from zero in both directi - point_matrix = Nx.tensor([x, y, 1], type: {:f, @tensor_data_type_bits}) - - Nx.tensor( - [ - [sx, 0, tx], - [0, sy, ty], - [0, 0, 1] - ], - type: {:f, @tensor_data_type_bits} - ) - |> Nx.dot(point_matrix) - |> Nx.to_flat_list() - |> then(fn [x_trans, y_trans, _] -> {x_trans, y_trans} end) + Algebra.transform_given_point(x, y, sx, sy, tx, ty) end def do_transform(%Dataset{x: x, y: y} = dataset, xlim, ylim, width, height, transition) do @@ -258,9 +246,11 @@ defmodule Matplotex.Figure.Areal do x |> Enum.zip(y) |> Enum.map(fn {x, y} -> - transformation(x, y, xlim, ylim, width, height, transition) - end) + x + |> transformation(y, xlim, ylim, width, height, transition) + |> Algebra.flip_y_coordinate() + end) %Dataset{dataset | transformed: transformed} end end diff --git a/lib/matplotex/figure/areal/bar_chart.ex b/lib/matplotex/figure/areal/bar_chart.ex index 753684c..c9ac286 100644 --- a/lib/matplotex/figure/areal/bar_chart.ex +++ b/lib/matplotex/figure/areal/bar_chart.ex @@ -9,6 +9,8 @@ defmodule Matplotex.Figure.Areal.BarChart do alias Matplotex.Figure.Areal use Areal + @xmin_value 0 + frame( legend: %Legend{}, coords: %Coords{}, @@ -33,12 +35,12 @@ defmodule Matplotex.Figure.Areal.BarChart do dataset = Dataset.cast(%Dataset{x: x, y: values, pos: pos, width: width}, opts) datasets = data ++ [dataset] xydata = flatten_for_data(datasets) - %Figure{figure | axes: %{axes | data: xydata, dataset: datasets}} + %Figure{figure | rc_params: %RcParams{white_space: width, y_padding: 0}, axes: %{axes | data: xydata, dataset: datasets}} end @impl Areal def materialize(figure) do - __MODULE__.materialized(figure) + __MODULE__.materialized_by_region(figure) |> materialize_bars() end @@ -48,24 +50,32 @@ defmodule Matplotex.Figure.Areal.BarChart do %{ dataset: data, limit: %{x: xlim, y: ylim}, - size: {width, height}, - coords: %Coords{bottom_left: {blx, bly}}, + region_content: %Region{ + x: x_region_content, + y: y_region_content, + width: width_region_content, + height: height_region_content + }, element: elements } = axes, - rc_params: %RcParams{x_padding: padding} + rc_params: %RcParams{x_padding: x_padding, white_space: white_space} } = figure ) do - px = width * padding - width = width - px * 2 - py = height * padding - height = height - py * 2 + x_padding_value = width_region_content * x_padding + white_space + shrinked_width_region_content = width_region_content - x_padding_value * 2 bar_elements = data |> Enum.map(fn dataset -> dataset - |> do_transform(xlim, ylim, width, height, {blx + px, bly + py}) - |> capture(bly) + |> do_transform( + xlim, + ylim, + shrinked_width_region_content, + height_region_content, + {x_region_content + x_padding_value, y_region_content} + ) + |> capture(-y_region_content) end) |> List.flatten() @@ -92,7 +102,7 @@ defmodule Matplotex.Figure.Areal.BarChart do def generate_ticks(data) do max = Enum.max(data) step = max |> round_to_best() |> div(5) |> round_to_best() - {list_of_ticks(data, step), {0, max}} + {list_of_ticks(data, step), {@xmin_value, max}} end def generate_ticks(side, {min, max} = lim) do @@ -123,7 +133,7 @@ defmodule Matplotex.Figure.Areal.BarChart do x: bar_position(x, pos_factor), y: y, width: width, - height: y - bly, + height: bly - y, color: color } ], @@ -135,7 +145,8 @@ defmodule Matplotex.Figure.Areal.BarChart do defp capture([], captured, _dataset, _bly), do: captured defp hypox(y) do - 1..length(y) |> Enum.into([]) + nof_x = length(y) + @xmin_value|>Nx.linspace(nof_x, n: nof_x)|>Nx.to_list() end defp bar_position(x, pos_factor) when pos_factor < 0 do diff --git a/lib/matplotex/figure/areal/line_plot.ex b/lib/matplotex/figure/areal/line_plot.ex index 1bbc752..4ea11e0 100644 --- a/lib/matplotex/figure/areal/line_plot.ex +++ b/lib/matplotex/figure/areal/line_plot.ex @@ -43,7 +43,7 @@ defmodule Matplotex.Figure.Areal.LinePlot do @impl Areal def materialize(figure) do figure - |> __MODULE__.materialized() + |> __MODULE__.materialized_by_region() |> materialize_lines() end @@ -53,23 +53,34 @@ defmodule Matplotex.Figure.Areal.LinePlot do %{ dataset: data, limit: %{x: xlim, y: ylim}, - size: {width, height}, - coords: %Coords{bottom_left: {blx, bly}}, + region_content: %Region{ + x: x_region_content, + y: y_region_content, + width: width_region_content, + height: height_region_content + }, element: elements } = axes, rc_params: %RcParams{x_padding: x_padding, y_padding: y_padding} } = figure ) do - px = width * x_padding - py = height * y_padding - width = width - px * 2 - height = height - py * 2 + # {x_region_content, y_region_content} = Algebra.flip_y_coordinate({x_region_content, y_region_content}) + x_padding_value = width_region_content * x_padding + y_padding_value = height_region_content * y_padding + shrinked_width_region_content = width_region_content - x_padding_value * 2 + shrinked_height_region_content = height_region_content - y_padding_value * 2 line_elements = data |> Enum.map(fn dataset -> dataset - |> do_transform(xlim, ylim, width, height, {blx + px, bly + py}) + |> do_transform( + xlim, + ylim, + shrinked_width_region_content, + shrinked_height_region_content, + {x_region_content + x_padding_value, y_region_content + y_padding_value} + ) |> capture() end) |> List.flatten() diff --git a/lib/matplotex/figure/areal/region.ex b/lib/matplotex/figure/areal/region.ex index 213eb39..e4ad9f6 100644 --- a/lib/matplotex/figure/areal/region.ex +++ b/lib/matplotex/figure/areal/region.ex @@ -1,3 +1,13 @@ defmodule Matplotex.Figure.Areal.Region do - defstruct [:x, :y, :width, :height, :name, :theta, :coords] + alias Matplotex.Figure.Areal.XyRegion.Coords + @zero_by_default 0 + defstruct x: @zero_by_default, y: @zero_by_default, width: @zero_by_default, height: @zero_by_default, name: nil, theta: @zero_by_default, coords: nil + + def get_label_coords(%__MODULE__{x: x, y: y, coords: %Coords{label: {label_x, label_y}}}) do + {x + label_x, y + label_y} + end + + def get_tick_coords(%__MODULE__{x: x, y: y, coords: %Coords{ticks: {tick_x, tick_y}}}) do + {x + tick_x, y + tick_y} + end end diff --git a/lib/matplotex/figure/areal/scatter.ex b/lib/matplotex/figure/areal/scatter.ex index 3bddbfd..15846af 100644 --- a/lib/matplotex/figure/areal/scatter.ex +++ b/lib/matplotex/figure/areal/scatter.ex @@ -38,7 +38,7 @@ defmodule Matplotex.Figure.Areal.Scatter do @impl Areal def materialize(figure) do - __MODULE__.materialized(figure) + __MODULE__.materialized_by_region(figure) |> materialize_elements() end @@ -48,23 +48,33 @@ defmodule Matplotex.Figure.Areal.Scatter do %{ dataset: data, limit: %{x: xlim, y: ylim}, - size: {width, height}, - coords: %Coords{bottom_left: {blx, bly}}, + region_content: %Region{ + x: x_region_content, + y: y_region_content, + width: width_region_content, + height: height_region_content + }, element: elements } = axes, rc_params: %RcParams{x_padding: x_padding, y_padding: y_padding} } = figure ) do - px = width * x_padding - py = height * y_padding - width = width - px * 2 - height = height - py * 2 + x_padding_value = width_region_content * x_padding + y_padding_value = height_region_content * y_padding + shrinked_width_region_content = width_region_content - x_padding_value * 2 + shrinked_height_region_content = height_region_content - y_padding_value * 2 line_elements = data |> Enum.map(fn dataset -> dataset - |> do_transform(xlim, ylim, width, height, {blx + px, bly + py}) + |> do_transform( + xlim, + ylim, + shrinked_width_region_content, + shrinked_height_region_content, + {x_region_content + x_padding_value, y_region_content + y_padding_value} + ) |> capture() end) |> List.flatten() @@ -74,7 +84,7 @@ defmodule Matplotex.Figure.Areal.Scatter do end def materialize(xystream, figure) do - __MODULE__.materialized(figure) + __MODULE__.materialized_by_region(figure) |> material_stream(xystream) end diff --git a/lib/matplotex/figure/areal/xy_region/coords.ex b/lib/matplotex/figure/areal/xy_region/coords.ex index db4383d..aef9d2b 100644 --- a/lib/matplotex/figure/areal/xy_region/coords.ex +++ b/lib/matplotex/figure/areal/xy_region/coords.ex @@ -1,3 +1,3 @@ defmodule Matplotex.Figure.Areal.XyRegion.Coords do - defstruct [:label, :ticks] + defstruct [:label, :ticks, :grids] end diff --git a/lib/matplotex/figure/cast.ex b/lib/matplotex/figure/cast.ex index 8b06687..73b8dcb 100644 --- a/lib/matplotex/figure/cast.ex +++ b/lib/matplotex/figure/cast.ex @@ -1,4 +1,7 @@ defmodule Matplotex.Figure.Cast do + alias Matplotex.Utils.Algebra + alias Matplotex.Figure.Areal.XyRegion.Coords, as: XyCoords + alias Matplotex.Figure.Areal.Region alias Matplotex.Figure.Dataset alias Matplotex.Element.Tick alias Matplotex.Figure.RcParams @@ -11,6 +14,8 @@ defmodule Matplotex.Figure.Cast do @ytick_type "figure.y_tick" @stroke_grid "#ddd" @stroke_width_grid 1 + @lowest_tick 0 + @zero_to_move 0 def cast_spines( %Figure{ @@ -66,6 +71,67 @@ defmodule Matplotex.Figure.Cast do raise ArgumentError, message: "Figure does't contain enough data to proceed" end + def cast_spines_by_region( + %Figure{ + axes: + %{ + region_content: %Region{ + x: content_x, + y: content_y, + width: content_width, + height: content_height + }, + element: elements + } = axes + } = figure + ) do + # Convert to the svg plane + + {left_x, bottom_y} = Algebra.flip_y_coordinate({content_x, content_y}) + + {right_x, top_y} = + Algebra.transform_given_point(content_width, content_height, content_x, content_y, 0) + |> Algebra.flip_y_coordinate() + + # Four Line struct representing each corners + + left = %Line{ + x1: left_x, + y1: bottom_y, + x2: left_x, + y2: top_y, + type: "spine.left" + } + + right = %Line{ + x1: right_x, + y1: bottom_y, + x2: right_x, + y2: top_y, + type: "spine.right" + } + + top = %Line{ + x1: left_x, + y1: top_y, + x2: right_x, + y2: top_y, + type: "spine.top" + } + + bottom = %Line{ + x1: left_x, + y1: bottom_y, + x2: right_x, + y2: bottom_y, + type: "spine.bottom" + } + + %Figure{figure | axes: %{axes | element: elements ++ [left, right, top, bottom]}} + end + + def cast_spines_by_region(figure), do: figure + def cast_border( %Figure{ axes: %{border: {lx, by, rx, ty}, element: element} = axes, @@ -119,37 +185,32 @@ defmodule Matplotex.Figure.Cast do raise ArgumentError, message: "Invalid figure to cast title" end - def cast_label(%Figure{axes: %{label: nil}} = figure), do: figure + def cast_title_by_region( + %Figure{ + axes: + %{ + region_title: region, + title: title, + element: elements + } = axes, + rc_params: %RcParams{title_font: title_font, label_padding: title_padding} + } = figure + ) do + {title_x, title_y} = region |> calculate_center(:x) |> Algebra.flip_y_coordinate() - # def cast_label( - # %Figure{ - # axes: %{label: labels, coords: coords, element: element} = axes, - # rc_params: rc_params - # } = figure - # ) do - # label_elements = - # Enum.map(labels, fn {axis, label} -> - # {x, y} = label_coords = Map.get(coords, :"#{axis}_label") - # ticks_coords = Map.get(coords, :"#{axis}_ticks") - # height = calculate_distance(label_coords, ticks_coords) - # width = calculate_width(coords, axis) - # label_font = rc_params |> RcParams.get_rc(:"get_#{axis}_label_font") |> Map.from_struct() - - # %Label{ - # type: "figure.#{axis}_label", - # x: x, - # y: y, - # text: label, - # height: height, - # width: width, - # rotate: rotate_label(axis) - # } - # |> merge_structs(label_font) - # end) - - # element = element ++ label_elements - # %Figure{figure | axes: %{axes | element: element}} - # end + title = + %Label{ + type: "figure.title", + x: title_x, + y: title_y + title_padding, + text: title + } + |> Label.cast_label(title_font) + + %Figure{figure | axes: %{axes | element: elements ++ [title]}} + end + + def cast_label(%Figure{axes: %{label: nil}} = figure), do: figure def cast_label(figure) do figure @@ -157,9 +218,11 @@ defmodule Matplotex.Figure.Cast do |> cast_ylabel() end - # def cast_label(_figure) do - # raise ArgumentError, message: "Invalid figure to cast label" - # end + def cast_label_by_region(figure) do + figure + |> cast_xlabel_by_region() + |> cast_ylabel_by_region() + end def cast_xlabel( %Figure{ @@ -186,6 +249,29 @@ defmodule Matplotex.Figure.Cast do def cast_xlabel(figure), do: figure + def cast_xlabel_by_region( + %Figure{ + axes: %{label: %{x: x_label}, region_x: region_x, element: element} = axes, + rc_params: %RcParams{x_label_font: x_label_font} + } = figure + ) + when not is_nil(x_label) do + # {_, x_label_y} = Region.get_label_coords(region_x) + {x_label_x, x_label_y} = region_x |> calculate_center(:x) |> Algebra.flip_y_coordinate() + + x_label = + %Label{ + type: "figure.x_label", + x: x_label_x, + y: x_label_y, + text: x_label + } + |> Label.cast_label(x_label_font) + + element = element ++ [x_label] + %Figure{figure | axes: %{axes | element: element}} + end + def cast_ylabel( %Figure{ axes: %{label: %{y: y_label}, coords: coords, element: element} = axes, @@ -213,6 +299,29 @@ defmodule Matplotex.Figure.Cast do def cast_ylabel(figure), do: figure + def cast_ylabel_by_region( + %Figure{ + axes: %{label: %{y: y_label}, region_y: region_y, element: element} = axes, + rc_params: %RcParams{y_label_font: y_label_font} + } = figure + ) + when not is_nil(y_label) do + {y_label_x, y_label_y} = region_y |> calculate_center(:y) |> Algebra.flip_y_coordinate() + + y_label = + %Label{ + type: "figure.y_label", + x: y_label_x, + y: y_label_y, + text: y_label, + rotate: rotate_label(:y) + } + |> Label.cast_label(y_label_font) + + element = element ++ [y_label] + %Figure{figure | axes: %{axes | element: element}} + end + def cast_xticks( %Figure{ axes: @@ -293,6 +402,107 @@ defmodule Matplotex.Figure.Cast do def cast_xticks(%Figure{} = figure), do: figure + def cast_xticks_by_region( + %Figure{ + axes: + %{ + tick: %{x: x_ticks}, + limit: %{x: {_min, _max} = xlim}, + region_x: + %Region{ + x: x_region_x, + y: y_region_x, + height: height_region_x, + width: width_region_x, + coords: %XyCoords{ticks: {_, y_x_tick}} = coords_region_x + } = region_x, + data: {x_data, y_data}, + dataset: dataset, + element: elements, + show_x_ticks: true + } = axes, + rc_params: %RcParams{ + x_tick_font: x_tick_font, + x_padding: x_padding, + tick_line_length: tick_line_length, + white_space: white_space + } + } = figure + ) + when is_list(x_ticks) do + + x_ticks = confine_ticks(x_ticks, xlim) + x_data = confine_data(x_data, xlim) + dataset = confine_data(dataset, xlim, :x) + x_padding_value = width_region_x * x_padding + white_space + shrinked_width_region_x = width_region_x - x_padding_value * 2 + x_region_x_with_padding = x_region_x + x_padding_value + + ticks_width_position = + x_ticks + |> length() + |> content_linespace(shrinked_width_region_x) + |> Enum.zip(x_ticks) + + {x_tick_elements, vgrid_coords} = + Enum.map(ticks_width_position, fn {tick_position, label} -> + {_, y_x_tick_line} = + @zero_to_move + |>Algebra.transform_given_point(height_region_x, x_region_x, y_region_x) + |> Algebra.flip_y_coordinate() + + {x_x_tick, y_x_tick} = + tick_position + |>Algebra.transform_given_point( + @zero_to_move, + x_region_x_with_padding, + y_x_tick + ) + |> Algebra.flip_y_coordinate() + + label = + %Label{ + type: @xtick_type, + x: x_x_tick, + y: y_x_tick, + text: format_tick_label(label) + } + |> Label.cast_label(x_tick_font) + + line = %Line{ + type: @xtick_type, + x1: x_x_tick, + y1: y_x_tick_line, + x2: x_x_tick, + y2: y_x_tick_line + tick_line_length + } + + {%Tick{type: @xtick_type, tick_line: line, label: label}, {x_x_tick, y_x_tick_line}} + end) + |> Enum.unzip() + elements = elements ++ x_tick_elements + + %Figure{ + figure + | axes: %{ + axes + | data: {x_data, y_data}, + dataset: dataset, + element: elements, + region_x: %Region{region_x | coords: %XyCoords{coords_region_x | grids: vgrid_coords}} + } + } + end + + def cast_xticks_by_region(figure), do: figure + defp format_tick_label({label, _index}), do: label + defp format_tick_label(label) when is_float(label), do: Float.round(label, 2) + defp format_tick_label(label), do: label + defp content_linespace(number_of_ticks_required, axis_size) do + @lowest_tick |> Nx.linspace(axis_size, n: number_of_ticks_required) |> Nx.to_list() + end + + @spec cast_yticks(Matplotex.Figure.t()) :: Matplotex.Figure.t() def cast_yticks( %Figure{ axes: @@ -373,6 +583,97 @@ defmodule Matplotex.Figure.Cast do def cast_ytick(%Figure{} = figure), do: figure + def cast_yticks_by_region( + %Figure{ + axes: + %{ + tick: %{y: y_ticks}, + region_y: + %Region{ + x: x_region_y, + y: y_region_y, + width: width_region_y, + height: height_region_y, + coords: %XyCoords{ticks: {x_y_tick, _}} = coords_region_y + } = region_y, + element: elements, + limit: %{y: {_min, _max} = ylim}, + data: {x_data, y_data}, + dataset: dataset, + show_y_ticks: true + } = axes, + rc_params: %RcParams{ + y_tick_font: y_tick_font, + y_padding: y_padding, + tick_line_length: tick_line_length + } + } = figure + ) do + y_ticks = confine_ticks(y_ticks, ylim) + y_data = confine_data(y_data, ylim) + dataset = confine_data(dataset, ylim, :y) + + y_padding_value = height_region_y * y_padding + shrinked_height_region_y = height_region_y - y_padding_value * 2 + y_region_y_with_padding = y_region_y + y_padding_value + + ticks_width_position = + y_ticks + |> length() + |> content_linespace(shrinked_height_region_y) + |> Enum.zip(y_ticks) + + {ytick_elements, hgrid_coords} = + Enum.map(ticks_width_position, fn {tick_position, label} -> + {x_y_tick_line, _} = + Algebra.transform_given_point(width_region_y, @zero_to_move, x_region_y, y_region_y) + + {x_y_tick, y_y_tick} = + @zero_to_move + |>Algebra.transform_given_point( + tick_position, + x_y_tick, + y_region_y_with_padding + ) + |> Algebra.flip_y_coordinate() + + label = + %Label{ + type: @ytick_type, + y: y_y_tick, + x: x_y_tick, + text: format_tick_label(label) + } + |> Label.cast_label(y_tick_font) + + line = %Line{ + type: @ytick_type, + y1: y_y_tick, + x1: x_y_tick_line, + x2: x_y_tick_line - tick_line_length, + y2: y_y_tick + } + + {%Tick{type: @ytick_type, tick_line: line, label: label}, {x_y_tick_line, y_y_tick}} + end) + |> Enum.unzip() + + elements = elements ++ ytick_elements + + %Figure{ + figure + | axes: %{ + axes + | data: {x_data, y_data}, + dataset: dataset, + element: elements, + region_y: %Region{region_y | coords: %XyCoords{coords_region_y | grids: hgrid_coords}} + } + } + end + + def cast_yticks_by_region(figure), do: figure + def cast_hgrids(%Figure{axes: %{coords: %{hgrids: nil}}} = figure), do: figure def cast_hgrids(%Figure{axes: %{show_h_grid: false}} = figure), do: figure @@ -404,6 +705,37 @@ defmodule Matplotex.Figure.Cast do %Figure{figure | axes: %{axes | element: elements}} end + def cast_hgrids_by_region( + %Figure{ + axes: + %{ + show_h_grid: true, + region_y: %Region{coords: %XyCoords{grids: hgrids}}, + region_x: %Region{width: width_region_x}, + element: elements + } = axes + } = figure + ) do + hgrid_elements = + Enum.map(hgrids, fn {x, y} -> + %Line{ + x1: x, + x2: x + width_region_x, + y1: y, + y2: y, + type: "figure.h_grid", + stroke: @stroke_grid, + stroke_width: @stroke_width_grid + } + end) + + elements = elements ++ hgrid_elements + + %Figure{figure | axes: %{axes | element: elements}} + end + + def cast_hgrids_by_region(figure), do: figure + def cast_vgrids(%Figure{axes: %{coords: %{vgrids: nil}}} = figure), do: figure def cast_vgrids(%Figure{axes: %{show_v_grid: false}} = figure), do: figure @@ -435,6 +767,37 @@ defmodule Matplotex.Figure.Cast do %Figure{figure | axes: %{axes | element: elements}} end + def cast_vgrids_by_region( + %Figure{ + axes: + %{ + show_v_grid: true, + region_x: %Region{coords: %XyCoords{grids: vgrids}}, + region_y: %Region{height: height_region_y}, + element: elements + } = axes + } = figure + ) do + vgrid_elements = + Enum.map(vgrids, fn {x, y} -> + %Line{ + x1: x, + x2: x, + y1: y, + y2: y - height_region_y, + type: "figure.v_grid", + stroke: @stroke_grid, + stroke_width: @stroke_width_grid + } + end) + + elements = elements ++ vgrid_elements + + %Figure{figure | axes: %{axes | element: elements}} + end + + def cast_vgrids_by_region(figure), do: figure + defp plotify_tick(module, {label, value}, lim, axis_size, transition, data, axis) do {module.plotify(value, lim, axis_size, transition, data, axis), label} end @@ -461,6 +824,14 @@ defmodule Matplotex.Figure.Cast do {x, calculate_distance(bottom_left, top_left) / 2 + y} end + defp calculate_center(%Region{x: x, y: y, width: width}, :x) do + {calculate_distance({x, y}, {x + width, y}) / 2 + x, y} + end + + defp calculate_center(%Region{x: x, y: y, height: height}, :y) do + {x, calculate_distance({x, y}, {x, y + height}) / 2 + y} + end + defp merge_structs(%module{} = st, sst) do sst = Map.from_struct(sst) params = st |> Map.from_struct() |> Map.merge(sst) @@ -499,9 +870,8 @@ defmodule Matplotex.Figure.Cast do end) end - defp confine_ticks(ticks, {min, max} = lim) do + defp confine_ticks(ticks, {min, max}) do ticks - |> append_lim(lim) |> Enum.filter(fn tick -> tick >= min && tick <= max end) @@ -525,18 +895,18 @@ defmodule Matplotex.Figure.Cast do end) end - defp append_lim([first | [second | _]] = ticks, {min, max}) do - with_min = - if Enum.min(ticks) > min + (second - first) do - [min] ++ ticks - else - ticks - end - - if Enum.max(with_min) < max - (second - first) do - with_min ++ [max] - else - with_min - end - end + # defp append_lim([first | [second | _]] = ticks, {min, max}) do + # with_min = + # if Enum.min(ticks) > min + (second - first) do + # [min] ++ ticks + # else + # ticks + # end + + # if Enum.max(with_min) < max - (second - first) do + # with_min ++ [max] + # else + # with_min + # end + # end end diff --git a/lib/matplotex/figure/font.ex b/lib/matplotex/figure/font.ex index d764f33..e414e73 100644 --- a/lib/matplotex/figure/font.ex +++ b/lib/matplotex/figure/font.ex @@ -8,6 +8,8 @@ defmodule Matplotex.Figure.Font do @pt_to_inch_ratio 1 / 72 @text_rotation 0 @flate 0 + @dominant_baseline "middle" + @default_text_anchor "middle" defstruct font_size: @default_font_size, font_style: @default_font_style, @@ -17,6 +19,8 @@ defmodule Matplotex.Figure.Font do unit_of_measurement: @font_unit, pt_to_inch_ratio: @pt_to_inch_ratio, rotation: @text_rotation, + dominant_baseline: @dominant_baseline, + text_anchor: @default_text_anchor, flate: @flate def font_keys() do @@ -31,7 +35,18 @@ defmodule Matplotex.Figure.Font do struct(__MODULE__, params) end - def update(font, params) do - struct(font, params) + def update(font, params, element) do + update_font(font, params, element) + end + + defp update_font(font, params, element) do + font + |> Map.from_struct() + |> Map.keys() + |> Enum.reduce(font, fn key, acc -> + existing_value = Map.get(font, key) + value = Map.get(params, :"#{element}_#{key}", existing_value) + Map.put(acc, key, value) + end) end end diff --git a/lib/matplotex/figure/lead.ex b/lib/matplotex/figure/lead.ex index 47709ec..28ff82b 100644 --- a/lib/matplotex/figure/lead.ex +++ b/lib/matplotex/figure/lead.ex @@ -1,35 +1,30 @@ defmodule Matplotex.Figure.Lead do + alias Matplotex.Utils.Algebra alias Matplotex.Figure.Areal.XyRegion.Coords, as: XyCoords alias Matplotex.Figure.Font alias Matplotex.Figure.Areal.Region alias Matplotex.Figure.TwoD - alias Matplotex.Figure.Dimension alias Matplotex.Figure.Coords alias Matplotex.Figure.RcParams alias Matplotex.Figure @pt_to_inch 1 / 150 @padding 10 / 96 - @tick_line_offset 5 / 96 + @zero_to_move 0 - def set_spines(%Figure{} = figure) do - figure - |> set_xlabel_coords() - |> set_ylabel_coords() - |> set_title_coords() - |> set_xticks_coords() - |> set_yticks_coords() - |> set_border_coords() - end + @spec set_regions(Matplotex.Figure.t()) :: Matplotex.Figure.t() - def set_regions(%Figure{} = figure) do + def set_regions(%Figure{figsize: {width, height}} = figure) when width > 0 and height > 0 do figure |> set_frame_size() + |> ensure_ticks_are_valid() |> set_region_xy() |> set_region_title() |> set_region_legend() |> set_region_content() end + def set_regions(figure), do: figure + def set_border(%Figure{margin: margin, axes: axes, figsize: {fig_width, fig_height}} = figure) do margin = margin / 2 lx = fig_width * margin @@ -41,7 +36,59 @@ defmodule Matplotex.Figure.Lead do defp set_frame_size(%Figure{margin: margin, figsize: {fwidth, fheight}, axes: axes} = figure) do frame_size = {fwidth - fwidth * 2 * margin, fheight - fheight * 2 * margin} - %Figure{figure | axes: %{axes | size: frame_size}} + lx = fwidth * margin + ty = -fheight * margin + rx = fwidth - fwidth * margin + by = fheight * margin - fheight + %Figure{figure | axes: %{axes | size: frame_size, border: {lx, by, rx, ty}}} + end + + defp ensure_ticks_are_valid( + %Figure{ + figsize: {width, height}, + axes: + %{ + data: {x_data, y_data}, + tick: %TwoD{x: x_ticks, y: y_ticks} = ticks, + limit: %TwoD{x: x_lim, y: y_lim} = limit + } = axes + } = figure + ) do + {x_ticks, x_lim} = maybe_generate_ticks(x_ticks, x_lim, x_data, width) + {y_ticks, y_lim} = maybe_generate_ticks(y_ticks, y_lim, y_data, height) + %Figure{ + figure + | axes: %{ + axes + | tick: %TwoD{ticks | x: x_ticks, y: y_ticks}, + limit: %TwoD{limit | x: x_lim, y: y_lim} + } + } + 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} + end + end + + defp generate_ticks(nil, data, number_of_ticks) do + {min, upper_limit} = Enum.min_max(data) + + lower_limit = + if min < 0 do + min + else + 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} end defp set_region_xy( @@ -52,34 +99,42 @@ defmodule Matplotex.Figure.Lead do region_y: region_y, label: %TwoD{y: y_label, x: x_label}, tick: %TwoD{y: y_ticks, x: x_ticks}, - size: {f_width, f_height} + size: {f_width, f_height}, + border: {lx, by, _rx, _ty} } = axes, rc_params: %RcParams{ x_label_font: x_label_font, x_tick_font: x_tick_font, y_label_font: y_label_font, y_tick_font: y_tick_font, - label_padding: label_padding, tick_line_length: tick_line_length } } = figure ) do - # region_x = %Region{x: total space required for ylabel plus yticks plus ytickline plus ypadding y: 0} 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) - x_region_x = - space_required_for_region_y = - [space_for_ylabel, y_tick, space_for_yticks, label_padding, tick_line_length] |> Enum.sum() + space_required_for_region_y = + [space_for_ylabel, space_for_yticks, tick_line_length] |> Enum.sum() space_for_x_label = height_required_for_text(x_label_font, x_label) x_tick = Enum.max_by(x_ticks, &tick_length/1) space_for_x_tick = height_required_for_text(x_tick_font, x_tick) - y_region_y = - space_required_for_region_x = - [space_for_x_label, x_tick, space_for_x_tick, label_padding, tick_line_length] |> Enum.sum() + space_required_for_region_x = + [space_for_x_label, space_for_x_tick, tick_line_length] |> Enum.sum() + + {x_region_x, y_region_x} = + Algebra.transform_given_point(space_required_for_region_y, @zero_to_move, lx, by) + + {x_region_y, y_region_y} = + Algebra.transform_given_point(@zero_to_move, space_required_for_region_x, lx, by) + + x_label_coords = Algebra.transform_given_point(@zero_to_move, @zero_to_move, x_region_x, y_region_x) + x_tick_coords = Algebra.transform_given_point(@zero_to_move, space_for_x_label, x_region_x, y_region_x) + y_label_coords = Algebra.transform_given_point(@zero_to_move, @zero_to_move, x_region_y, y_region_y) + y_tick_coords = Algebra.transform_given_point(space_for_ylabel, @zero_to_move, x_region_y, y_region_y) %Figure{ figure @@ -88,18 +143,18 @@ defmodule Matplotex.Figure.Lead do | region_x: %Region{ region_x | x: x_region_x, - y: 0, + y: y_region_x, height: space_required_for_region_x, width: f_width - space_required_for_region_y, - coords: %XyCoords{label: {x_region_x, 0}, ticks: {x_region_x, space_for_x_label}} + coords: %XyCoords{label: x_label_coords, ticks: x_tick_coords} }, region_y: %Region{ region_y - | x: 0, + | x: x_region_y, y: y_region_y, width: space_required_for_region_y, height: f_height - space_required_for_region_x, - coords: %XyCoords{label: {0, y_region_y}, ticks: {space_for_x_label, y_region_y}} + coords: %XyCoords{label: y_label_coords, ticks: y_tick_coords} } } } @@ -113,23 +168,27 @@ defmodule Matplotex.Figure.Lead do %{ title: title, region_x: %Region{width: region_x_width}, - region_y: %Region{width: region_y_width, height: region_y_height} = region_y, + region_y: %Region{height: region_y_height} = region_y, region_title: region_title, - size: {_f_width, f_height} + size: {_f_width, _f_height}, + border: {lx, _by, _, ty} } = axes, rc_params: %RcParams{title_font: title_font} } = figure ) do space_for_title = height_required_for_text(title_font, title) + {x_region_title, y_region_title} = + Algebra.transform_given_point(@zero_to_move, -space_for_title, lx, ty, 0) + %Figure{ figure | axes: %{ axes | region_title: %Region{ region_title - | x: region_y_width, - y: f_height - space_for_title, + | x: x_region_title, + y: y_region_title + space_for_title, width: region_x_width, height: space_for_title }, @@ -146,18 +205,20 @@ defmodule Matplotex.Figure.Lead do axes: %{ show_legend: true, - region_x: %Region{x: x_region_x, width: region_x_width} = region_x, - region_title: %Region{height: region_title_height} = region_title, + region_x: %Region{width: region_x_width} = region_x, + region_title: %Region{height: region_title_height}, region_legend: region_legend, - size: {f_width, f_height} + size: {f_width, _f_height}, + border: {_lx, by, rx, ty} } = axes, rc_params: %RcParams{legend_width: legend_width} } = figure ) do region_legend_width = f_width * legend_width region_x_width_after_legend = region_x_width - region_legend_width - legend_region_x = x_region_x + region_x_width_after_legend - legend_region_y = f_height - region_title_height + + {x_region_legend, y_region_legend} = + Algebra.transform_given_point(-region_legend_width, -region_title_height, rx, ty, 0) %Figure{ figure @@ -167,16 +228,12 @@ defmodule Matplotex.Figure.Lead do region_x | width: region_x_width_after_legend }, - region_title: %Region{ - region_title - | width: region_x_width_after_legend - }, region_legend: %Region{ region_legend - | x: legend_region_x, - y: legend_region_y, + | x: x_region_legend, + y: y_region_legend, width: region_legend_width, - height: legend_region_y + height: y_region_legend - by } } } @@ -211,171 +268,6 @@ defmodule Matplotex.Figure.Lead do defp set_region_content(figure), do: figure - defp set_xlabel_coords(%Figure{axes: %{tick: %{y: nil}, show_y_ticks: true}} = figure) do - figure - |> generate_yticks() - |> set_xlabel_coords() - end - - defp set_xlabel_coords( - %Figure{ - rc_params: rc_params, - margin: margin, - figsize: {f_width, f_height}, - axes: - %{coords: coords, label: %{y: ylabel}, tick: %{y: y_ticks}, dimension: dimension} = - axes - } = figure - ) do - y_label_font_size = RcParams.get_rc(rc_params, :get_y_label_font_size) - y_tick_font_size = RcParams.get_rc(rc_params, :get_y_tick_font_size) - label_offset = label_offset(ylabel, y_label_font_size) - y_tick_offset = ytick_offset(y_ticks, y_tick_font_size) - y_tick_height = label_offset(y_ticks, y_tick_font_size) - - x_labelx = - f_width * margin + label_offset + y_tick_offset - - %Figure{ - figure - | axes: %{ - axes - | coords: %Coords{coords | x_label: {x_labelx, f_height * margin}}, - dimension: %Dimension{ - dimension - | y_label: {0, label_offset}, - y_tick: {y_tick_offset, y_tick_height} - } - } - } - end - - defp set_xlabel_coords(%Figure{} = figure), do: figure - - defp set_ylabel_coords(%Figure{axes: %{tick: %{x: nil}, show_x_ticks: true}} = figure) do - figure - |> generate_xticks() - |> set_ylabel_coords() - end - - defp set_ylabel_coords( - %Figure{ - rc_params: rc_params, - margin: margin, - figsize: {f_width, f_height}, - axes: - %{coords: coords, dimension: dimension, label: %{x: xlabel}, tick: %{x: x_ticks}} = - axes - } = figure - ) do - x_label_font_size = RcParams.get_rc(rc_params, :get_x_label_font_size) - x_tick_font_size = RcParams.get_rc(rc_params, :get_x_tick_font_size) - xlabel_offset = label_offset(xlabel, x_label_font_size) - x_tick_offset = xtick_offset(x_ticks, x_tick_font_size) - x_tick_width = ytick_offset(x_ticks, x_tick_font_size) - - y_labely = - f_height * margin + xlabel_offset + x_tick_offset - - %Figure{ - figure - | axes: %{ - axes - | coords: %Coords{coords | y_label: {f_width * margin, y_labely}}, - dimension: %Dimension{ - dimension - | x_label: {0, xlabel_offset}, - x_tick: {x_tick_width, x_tick_offset} - } - } - } - end - - defp set_ylabel_coords(%Figure{} = figure), do: figure - - defp set_title_coords( - %Figure{ - margin: margin, - figsize: {_f_width, f_height}, - axes: %{coords: %Coords{x_label: {xlx, _xly}} = coords} = axes - } = figure - ) do - %Figure{ - figure - | axes: %{axes | coords: %Coords{coords | title: {xlx, f_height - f_height * margin}}} - } - end - - defp set_xticks_coords( - %Figure{ - rc_params: rc_params, - axes: %{coords: %Coords{x_label: {xlx, xly}} = coords, label: %{x: xlabel}} = axes - } = figure - ) do - x_label_font_size = RcParams.get_rc(rc_params, :get_x_label_font_size) - xlable_offset = label_offset(xlabel, x_label_font_size) - - %Figure{ - figure - | axes: %{axes | coords: %Coords{coords | x_ticks: {xlx + @padding, xly + xlable_offset}}} - } - end - - defp set_yticks_coords( - %Figure{ - rc_params: rc_params, - axes: %{coords: %Coords{y_label: {ylx, yly}} = coords, label: %{y: ylabel}} = axes - } = figure - ) do - y_label_font_size = RcParams.get_rc(rc_params, :get_y_label_font_size) - ylabel_offset = label_offset(ylabel, y_label_font_size) - - %Figure{ - figure - | axes: %{axes | coords: %Coords{coords | y_ticks: {ylx + ylabel_offset + @padding, yly}}} - } - end - - defp set_border_coords( - %Figure{ - margin: margin, - figsize: {f_width, _}, - rc_params: rc_params, - axes: - %{ - coords: %Coords{x_label: {xlx, _}, y_label: {_ylx, yly}, title: {_, ytt}} = coords, - title: title - } = - axes - } = figure - ) do - bottom_left = {xlx, yly} - title_font_size = rc_params |> RcParams.get_rc(:get_title_font_size) - title_offset = label_offset(title, title_font_size) - topy = ytt - title_offset - top_left = {xlx, topy} - rightx = f_width - f_width * margin - top_right = {rightx, topy} - bottom_right = {rightx, yly} - width = rightx - xlx - height = topy - yly - - %Figure{ - figure - | axes: %{ - axes - | coords: %Coords{ - coords - | bottom_left: bottom_left, - top_right: top_right, - bottom_right: bottom_right, - top_left: top_left - }, - size: {width, height} - } - } - end - def focus_to_origin( %Figure{ figsize: {width, height}, @@ -433,7 +325,7 @@ defmodule Matplotex.Figure.Lead do {{centerx, centery}, radius} end - # TODO: Sort out how the user gets the control on font of the all texts + defp label_offset(nil, _font_size), do: 0 defp label_offset("", _font_size), do: 0 @@ -445,16 +337,6 @@ defmodule Matplotex.Figure.Lead do defp label_offset(_label, font_size) do font_size * @pt_to_inch + @padding end - - defp ytick_offset(y_ticks, font_size) do - tick_size = y_ticks |> Enum.max_by(fn tick -> tick_length(tick) end) |> tick_length() - font_size * @pt_to_inch * tick_size + @tick_line_offset + @padding - end - - defp xtick_offset(xticks, font_size) do - label_offset(xticks, font_size) + @tick_line_offset - end - defp tick_length(tick) when is_integer(tick) do tick |> Integer.to_string() |> String.length() end @@ -464,21 +346,13 @@ defmodule Matplotex.Figure.Lead do end defp tick_length(tick) when is_float(tick) do - tick |> Float.to_string() |> String.length() + tick|>Float.round(2) |> Float.to_string() |> String.length() end defp tick_length({label, _v}) when is_binary(label) do String.length(label) end - defp generate_yticks(%Figure{axes: %module{} = axes} = figure) do - %Figure{figure | axes: module.generate_yticks(axes)} - end - - defp generate_xticks(%Figure{axes: %module{} = axes} = figure) do - %Figure{figure | axes: module.generate_xticks(axes)} - end - defp height_required_for_text( %Font{ font_size: font_size, @@ -487,8 +361,9 @@ defmodule Matplotex.Figure.Lead do rotation: 0 }, _text - ), - do: font_size * pt_to_inch_ratio + flate + ) do + to_number(font_size) * pt_to_inch_ratio + flate + end defp height_required_for_text( %Font{ @@ -499,7 +374,7 @@ defmodule Matplotex.Figure.Lead do }, text ) do - text_height = font_size * pt_to_inch_ratio + text_height = to_number(font_size) * pt_to_inch_ratio text_length = tick_length(text) * pt_to_inch_ratio rotation = deg_to_rad(rotation) height_for_rotation = :math.sin(rotation) * text_length @@ -515,7 +390,7 @@ defmodule Matplotex.Figure.Lead do }, text ), - do: tick_length(text) * font_size * pt_to_inch_ratio + flate + do: tick_length(text) * to_number(font_size) * (pt_to_inch_ratio/2) + flate defp length_required_for_text( %Font{ @@ -526,11 +401,22 @@ defmodule Matplotex.Figure.Lead do }, text ) do - text_length = tick_length(text) * font_size * pt_to_inch_ratio + text_length = tick_length(text) * to_number(font_size) * (pt_to_inch_ratio/2) rotation = deg_to_rad(rotation) leng_for_rotation = :math.cos(rotation) * text_length leng_for_rotation + flate end defp deg_to_rad(deg), do: deg * :math.pi() / 180 + defp to_number(font_size) when is_number(font_size), do: font_size + + defp to_number(font_size) when is_binary(font_size) do + font_size = String.trim(font_size, "pt") + + if String.contains?(font_size, ".") do + String.to_float(font_size) + else + String.to_integer(font_size) + end + end end diff --git a/lib/matplotex/figure/rc_params.ex b/lib/matplotex/figure/rc_params.ex index 4c72e46..1f963c2 100644 --- a/lib/matplotex/figure/rc_params.ex +++ b/lib/matplotex/figure/rc_params.ex @@ -19,8 +19,8 @@ defmodule Matplotex.Figure.RcParams do @label_padding 5 / 96 @default_legend_width_percentage 0.2 @default_legend_items_orientation :horizontal - defstruct x_tick_font: @font, - y_tick_font: @font, + defstruct x_tick_font: %Font{dominant_baseline: "middle"}, + y_tick_font: %Font{text_anchor: "start"}, x_label_font: @font, y_label_font: @font, title_font: %Font{font_size: @default_title_font_size}, @@ -44,6 +44,7 @@ defmodule Matplotex.Figure.RcParams do tick_line_length: @tick_line_length, x_padding: @chart_padding, y_padding: @chart_padding, + white_space: 0, label_padding: @label_padding, legend_width: @default_legend_width_percentage, legend_items_orientation: @default_legend_items_orientation @@ -128,61 +129,71 @@ defmodule Matplotex.Figure.RcParams do def update_with_font(rc_params, params) do rc_params - |> update_font(params, :x_label_font) - |> update_font(params, :y_label_font) - |> update_font(params, :x_tick_font) - |> update_font(params, :y_tick_font) - |> update_font(params, :title_font) + |> update_font(params, :x_label) + |> update_font(params, :y_label) + |> update_font(params, :x_tick) + |> update_font(params, :y_tick) + |> update_font(params, :title) end defp update_font( - %__MODULE__{x_label_font: x_label_font} = rc_params, - %{x_label_font_size: x_label_font_size}, - :x_label_font + rc_params, + params, + element ) do - %__MODULE__{ - rc_params - | x_label_font: Font.update(x_label_font, %{font_size: x_label_font_size}) - } + element_font_keys = font_associated_keys(element) + element_params = Map.take(params, element_font_keys) + font = Map.get(rc_params, :"#{element}_font") + updated_font = Font.update(font, element_params, element) + Map.put(rc_params, :"#{element}_font", updated_font) + end + + # defp update_font( + # %__MODULE__{y_label_font: y_label_font} = rc_params, + # %{y_label_font_size: y_label_font_size}, + # :y_label_font + # ) do + # %__MODULE__{ + # rc_params + # | y_label_font: Font.update(y_label_font, %{font_size: y_label_font_size}) + # } + # end + + # defp update_font( + # %__MODULE__{x_tick_font: x_tick_font} = rc_params, + # %{x_tick_font_size: x_tick_font_size}, + # :x_tick_font + # ) do + # %__MODULE__{rc_params | x_tick_font: Font.update(x_tick_font, %{font_size: x_tick_font_size})} + # end + + # defp update_font( + # %__MODULE__{y_tick_font: y_tick_font} = rc_params, + # %{y_tick_font_size: y_tick_font_size}, + # :y_tick_font + # ) do + # %__MODULE__{rc_params | y_tick_font: Font.update(y_tick_font, %{font_size: y_tick_font_size})} + # end + + # defp update_font( + # %__MODULE__{title_font: title_font} = rc_params, + # %{title_font_size: title_font_size}, + # :title_font + # ) do + # %__MODULE__{rc_params | title_font: Font.update(title_font, %{font_size: title_font_size})} + # end + + # defp update_font(rc_params, _, _), do: rc_params + + def font_associated_keys(element) do + %Font{} + |> Map.from_struct() + |> Map.keys() + |> Enum.map(fn fkey -> + :"#{element}_#{fkey}" + end) end - defp update_font( - %__MODULE__{y_label_font: y_label_font} = rc_params, - %{y_label_font_size: y_label_font_size}, - :y_label_font - ) do - %__MODULE__{ - rc_params - | y_label_font: Font.update(y_label_font, %{font_size: y_label_font_size}) - } - end - - defp update_font( - %__MODULE__{x_tick_font: x_tick_font} = rc_params, - %{x_tick_font_size: x_tick_font_size}, - :x_tick_font - ) do - %__MODULE__{rc_params | x_tick_font: Font.update(x_tick_font, %{font_size: x_tick_font_size})} - end - - defp update_font( - %__MODULE__{y_tick_font: y_tick_font} = rc_params, - %{y_tick_font_size: y_tick_font_size}, - :y_tick_font - ) do - %__MODULE__{rc_params | y_tick_font: Font.update(y_tick_font, %{font_size: y_tick_font_size})} - end - - defp update_font( - %__MODULE__{title_font: title_font} = rc_params, - %{title_font_size: title_font_size}, - :title_font - ) do - %__MODULE__{rc_params | title_font: Font.update(title_font, %{font_size: title_font_size})} - end - - defp update_font(rc_params, _, _), do: rc_params - defp convert_font_size(<> <> "pt") do String.to_integer(font_size) end diff --git a/lib/matplotex/figure/sketch.ex b/lib/matplotex/figure/sketch.ex index 98b53a1..9f6d71b 100644 --- a/lib/matplotex/figure/sketch.ex +++ b/lib/matplotex/figure/sketch.ex @@ -13,7 +13,6 @@ defmodule Matplotex.Figure.Sketch do def call(%Figure{axes: %{element: elements}, figsize: {width, height}}) do elements - |> flipy(height) |> build_elements() |> wrap_with_tag(width * @dpi, height * @dpi) end @@ -45,10 +44,4 @@ defmodule Matplotex.Figure.Sketch do ) end - - defp flipy(elements, height) do - Enum.map(elements, fn %module{} = elem -> - module.flipy(elem, height) - end) - end end diff --git a/lib/matplotex/helpers.ex b/lib/matplotex/helpers.ex index c587dc0..cbe8d5a 100644 --- a/lib/matplotex/helpers.ex +++ b/lib/matplotex/helpers.ex @@ -60,8 +60,8 @@ defmodule Matplotex.Helpers do x = [1, 2, 3, 4, 6, 6, 7] y = [1, 3, 4, 4, 5, 6, 7] - frame_width = 3 - frame_height = 3 + frame_width = 6 + frame_height = 6 size = {frame_width, frame_height} margin = 0.05 font_size = "16pt" @@ -163,16 +163,17 @@ defmodule Matplotex.Helpers do # end def scatter() do - x = [1, 2, 3, 4, 6, 6, 7] - y = [1, 3, 4, 4, 5, 6, 7] - - frame_width = 3 - frame_height = 3 + # x = [10, 20, 3, 4, 6, 6, 7] + # y = [1, 3, 4, 4, 5, 6, 7] + x = [0, 10, 20] + y = [0, 10, 20] + frame_width = 10 + frame_height = 10 size = {frame_width, frame_height} margin = 0.05 font_size = "16pt" title_font_size = "18pt" - ticks = [1, 2, 3, 4, 5, 6, 7] + ticks = [0, 10, 20] x |> Matplotex.scatter(y) @@ -182,8 +183,8 @@ defmodule Matplotex.Helpers do |> Matplotex.set_ylabel("Y Axis") |> Matplotex.set_xticks(ticks) |> Matplotex.set_yticks(ticks) - |> Matplotex.set_xlim({1, 7}) - |> Matplotex.set_ylim({1, 7}) + |> Matplotex.set_xlim({0, 20}) + |> Matplotex.set_ylim({0, 20}) # TODO: Setting limits are not taking the proper xy values |> Matplotex.set_rc_params( x_tick_font_size: font_size, @@ -211,6 +212,7 @@ defmodule Matplotex.Helpers do |> Matplotex.plot(x, y2, color: "red", linestyle: "--", marker: "^", label: "Dataset 2") |> Matplotex.plot(x, y3, color: "green", linestyle: "-.", marker: "s", label: "Dataset 3") |> Matplotex.set_title("Title") + |> Matplotex.set_xticks([1,2,3,4,5]) |> Matplotex.set_xlabel("X-Axis") |> Matplotex.set_ylabel("Y-Axis") |> Matplotex.show() @@ -218,19 +220,20 @@ defmodule Matplotex.Helpers do end def multi_bar() do - categories = ["apple", "banana", "fig"] - values1 = [22, 33, 28] - values2 = [53, 63, 59] + categories = ["apple", "banana", "fig", "avocado"] + values1 = [22, 33, 28, 34] + values2 = [53, 63, 59, 60] width = 0.22 Matplotex.bar(width, values1, width, label: "Dataset1", color: "#255199") |> Matplotex.bar(-width, values2, width, label: "Dataset2", color: "orange") |> Matplotex.set_xticks(categories) - |> Matplotex.figure(%{figsize: {3, 3}, margin: 0.05}) + |> Matplotex.figure(%{figsize: {10, 10}, margin: 0.05}) |> Matplotex.set_title("Bar chart") |> Matplotex.set_xlabel("X-axis") |> Matplotex.set_ylabel("Y-Axis") |> Matplotex.hide_v_grid() + |> Matplotex.set_ylim({0, 70}) |> Matplotex.show() |> copy() end @@ -248,7 +251,7 @@ defmodule Matplotex.Helpers do |> Matplotex.scatter(y1, color: "blue", linestyle: "_", marker: "o", label: "Dataset 1") |> Matplotex.scatter(x, y2, color: "red", linestyle: "--", marker: "^", label: "Dataset 2") |> Matplotex.scatter(x, y3, color: "green", linestyle: "-.", marker: "s", label: "Dataset 3") - |> Matplotex.figure(%{figsize: {3, 3}, margin: 0.05}) + |> Matplotex.figure(%{figsize: {8, 8}, margin: 0.05}) |> Matplotex.set_title("Title") |> Matplotex.set_xlabel("X-Axis") |> Matplotex.set_ylabel("Y-Axis") diff --git a/lib/matplotex/utils/algebra.ex b/lib/matplotex/utils/algebra.ex index ac8e3d2..1fde07f 100644 --- a/lib/matplotex/utils/algebra.ex +++ b/lib/matplotex/utils/algebra.ex @@ -49,7 +49,24 @@ defmodule Matplotex.Utils.Algebra do def svgfy(y, height), do: height - y - def transform_given_point(ox, oy, theta, px, py) do + def transform_given_point(x, y, sx, sy, tx, ty, theta \\ 0) do + point_matrix = Nx.tensor([x, y, 1], type: {:f, @tensor_data_type_bits}) + Nx.tensor( + [ + [sx * :math.cos(theta), sy * -:math.sin(theta), tx], + [sx * :math.sin(theta), sy * :math.cos(theta), ty], + [0, 0, 1] + ], + type: {:f, @tensor_data_type_bits} + ) + |> Nx.dot(point_matrix) + |> Nx.to_flat_list() + |> List.to_tuple() + |> then(fn {x, y, _} -> {x, y} end) + end + + def transform_given_point(x, y, ox, oy, theta \\ 0) do + point_matrix = Nx.tensor([x, y, 1], type: {:f, @tensor_data_type_bits}) Nx.tensor( [ [:math.cos(theta), -:math.sin(theta), ox], @@ -58,13 +75,13 @@ defmodule Matplotex.Utils.Algebra do ], type: {:f, @tensor_data_type_bits} ) - |> Nx.dot(Nx.tensor([px, py, 1], type: {:f, @tensor_data_type_bits})) + |> Nx.dot(point_matrix) |> Nx.to_flat_list() |> List.to_tuple() |> then(fn {x, y, _} -> {x, y} end) end - def flip_y_coordinate(x, y) do + def flip_y_coordinate({x, y}) do {x, -y} end end diff --git a/test/matplotex/element/label_test.exs b/test/matplotex/element/label_test.exs new file mode 100644 index 0000000..be13d71 --- /dev/null +++ b/test/matplotex/element/label_test.exs @@ -0,0 +1,15 @@ +defmodule Matplotex.Element.LabelTest do + use Matplotex.PlotCase + + alias Matplotex.Element.Label + alias Matplotex.Figure.Font + + describe "cast_label/2" do + test "merging label with font" do + font = %Font{font_size: 22} + label = %Label{x: 1, y: 1} + assert %Label{font_size: updated_font_size} = Label.cast_label(label, font) + assert updated_font_size == 22 + end + end +end diff --git a/test/matplotex/figure/cast_test.exs b/test/matplotex/figure/cast_test.exs index 31863f8..f78d994 100644 --- a/test/matplotex/figure/cast_test.exs +++ b/test/matplotex/figure/cast_test.exs @@ -5,88 +5,65 @@ defmodule Matplotex.Figure.CastTest do use Matplotex.PlotCase setup do - figure = Matplotex.FrameHelpers.sample_figure() |> Lead.set_spines() + figure = Matplotex.FrameHelpers.sample_figure() {:ok, %{figure: figure}} end - describe "cast_spines" do + + describe "cast_spines_by_region/1" do test "add elements for borders in axes", %{figure: figure} do - assert %Figure{axes: %{element: elements}} = Cast.cast_spines(figure) + figure = Lead.set_regions(figure) + assert %Figure{axes: %{element: elements}} = Cast.cast_spines_by_region(figure) - assert Enum.filter(elements, fn x -> x.type == "spine.top" end) |> length() == 1 - assert Enum.filter(elements, fn x -> x.type == "spine.bottom" end) |> length() == 1 - assert Enum.filter(elements, fn x -> x.type == "spine.right" end) |> length() == 1 - assert Enum.filter(elements, fn x -> x.type == "spine.left" end) |> length() == 1 + assert Enum.any?(elements, fn x -> x.type == "spine.top" end) + assert Enum.any?(elements, fn x -> x.type == "spine.bottom" end) + assert Enum.any?(elements, fn x -> x.type == "spine.right" end) + assert Enum.any?(elements, fn x -> x.type == "spine.left" end) end end - describe "cast_title" do + describe "cast_title_by_region/1" do test "add element for title in axes", %{figure: figure} do assert %Figure{axes: %{element: elements}} = figure - |> Lead.set_spines() - |> Cast.cast_spines() - |> Cast.cast_title() + |> Lead.set_regions() + |> Cast.cast_spines_by_region() + |> Cast.cast_title_by_region() - assert Enum.filter(elements, fn x -> x.type == "figure.title" end) |> length == 1 + assert Enum.any?(elements, fn x -> x.type == "figure.title" end) end end - describe "cast_label/1" do + describe "cast_label_by_region/1" do test "add element for label in axes", %{figure: figure} do assert %Figure{axes: %{element: elements}} = figure - |> Lead.set_spines() - |> Cast.cast_label() + |> Lead.set_regions() + |> Cast.cast_label_by_region() - assert Enum.filter(elements, fn x -> x.type == "figure.x_label" end) |> length() == 1 - assert Enum.filter(elements, fn x -> x.type == "figure.y_label" end) |> length() == 1 + assert Enum.any?(elements, fn x -> x.type == "figure.x_label" end) + assert Enum.any?(elements, fn x -> x.type == "figure.y_label" end) end end - describe "cast_xticks/1" do + describe "cast_xticks_by_region/1" do test "add element for tick in axes", %{figure: figure} do assert %Figure{axes: %{element: elements, tick: %{x: x_ticks, y: _y_ticks}}} = figure - |> Lead.set_spines() - |> Cast.cast_xticks() - - assert Enum.filter(elements, fn x -> x.type == "figure.x_tick" end) |> length() == - length(x_ticks) - end - - test "generates the ticks are getting confined within the limit", %{figure: figure} do - figure = Matplotex.set_xlim(figure, {2, 6}) - - %Figure{axes: %{element: elements}} = - figure - |> Lead.set_spines() - |> Cast.cast_xticks() - - assert Enum.filter(elements, fn x -> x.type == "figure.x_tick" end) |> length() == 5 - end - - test "ticks will get generated if no ticks added" do - x = [1, 3, 7, 4, 2, 5, 6] - y = [1, 3, 7, 4, 2, 5, 6] - figure = Matplotex.plot(x, y) - - %Figure{axes: %{data: {_x, _y}, tick: %{x: x_ticks}, element: elements}} = - figure - |> Lead.set_spines() - |> Cast.cast_xticks() + |> Lead.set_regions() + |> Cast.cast_xticks_by_region() assert Enum.filter(elements, fn x -> x.type == "figure.x_tick" end) |> length() == length(x_ticks) end end - describe "cast_yticks/1" do + describe "cast_yticks_by_region/1" do test "add element for tick in axes", %{figure: figure} do assert %Figure{axes: %{element: elements, tick: %{y: y_ticks}}} = figure - |> Lead.set_spines() - |> Cast.cast_yticks() + |> Lead.set_regions() + |> Cast.cast_yticks_by_region() assert Enum.filter(elements, fn x -> x.type == "figure.y_tick" end) |> length() == length(y_ticks) @@ -94,31 +71,27 @@ defmodule Matplotex.Figure.CastTest do end # TODO: Testcases for show and hide hgrid - describe "cast_hgrids/1" do + + describe "cast_hgrids_by_region/1" do test "add elements for horizontal grids", %{figure: figure} do assert %Figure{axes: %{element: elements, tick: %{y: y_ticks}}} = figure - |> Lead.set_spines() - |> Cast.cast_label() - |> Cast.cast_xticks() - |> Cast.cast_yticks() - |> Cast.cast_hgrids() + |> Lead.set_regions() + |> Cast.cast_yticks_by_region() + |> Cast.cast_hgrids_by_region() assert Enum.filter(elements, fn x -> x.type == "figure.h_grid" end) |> length() == length(y_ticks) end end - describe "cast_vgrids/1" do + describe "cast_vgrids_by_region/1" do test "add elements for vertical grids", %{figure: figure} do assert %Figure{axes: %{element: elements, tick: %{x: x_ticks}}} = figure - |> Lead.set_spines() - |> Cast.cast_label() - |> Cast.cast_xticks() - |> Cast.cast_yticks() - |> Cast.cast_hgrids() - |> Cast.cast_vgrids() + |> Lead.set_regions() + |> Cast.cast_xticks_by_region() + |> Cast.cast_vgrids_by_region() assert Enum.filter(elements, fn x -> x.type == "figure.v_grid" end) |> length() == length(x_ticks) diff --git a/test/matplotex/figure/lead_test.exs b/test/matplotex/figure/lead_test.exs index d846a57..5c1cd93 100644 --- a/test/matplotex/figure/lead_test.exs +++ b/test/matplotex/figure/lead_test.exs @@ -1,15 +1,10 @@ defmodule Matplotex.Figure.LeadTest do + alias Matplotex.Figure.TwoD alias Matplotex.Figure.Areal.Region - alias Matplotex.Figure.Coords - alias Matplotex.Figure.LinePlot alias Matplotex.Figure - alias Matplotex.Figure.Areal.LinePlot use Matplotex.PlotCase alias Matplotex.Figure.Lead - # all the padding and tickline spaces - @padding_and_tick_line_space 25 / 96 - @padding 10 / 96 setup do figure = Matplotex.FrameHelpers.sample_figure() @@ -33,224 +28,6 @@ defmodule Matplotex.Figure.LeadTest do {:ok, %{figure: figure, figure2: figure2}} end - describe "set_spines/1" do - test "sets coordinates of spines in a figure by reducing margin", %{figure: figure} do - frame_width = 8 - frame_height = 6 - - margin = 0.1 - font_size = 0 - title_font_size = 0 - - figure = - figure - |> Matplotex.figure(%{margin: margin}) - |> Matplotex.set_rc_params(%{ - 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 - }) - - assert %Figure{ - axes: %LinePlot{ - title: _title, - coords: %Coords{ - bottom_left: {blx, bly}, - bottom_right: {brx, bry}, - top_right: {trx, ytr}, - top_left: {tlx, tly} - } - } - } = - Lead.set_spines(figure) - - # Check bottom-left corner - assert blx == frame_width * margin + @padding_and_tick_line_space - - assert Float.floor(bly, 7) == - Float.floor(frame_height * margin + @padding_and_tick_line_space, 7) - - # Check bottom-right corner - assert brx == frame_width - frame_width * margin - # + @padding_and_tick_line_space - assert Float.floor(bry, 8) == - Float.floor(frame_height * margin + @padding_and_tick_line_space, 8) - - # Check top-right corner - assert trx == frame_width - frame_width * margin - # + @padding_and_tick_line_space - assert ytr == frame_height - frame_height * margin - @padding - # + @padding_and_tick_line_space - # Check top-left corner - assert tlx == frame_width * margin + @padding_and_tick_line_space - assert tly == frame_height - frame_height * margin - @padding - end - - test "sets coordinates by reducing title height", %{figure: figure} do - frame_height = 6 - - margin = 0 - font_size = 0 - title_font_size = 18 - - figure = - figure - |> Matplotex.figure(%{margin: margin}) - |> Matplotex.set_rc_params( - 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 - ) - - assert %Figure{ - axes: %LinePlot{ - coords: %Coords{ - top_left: {_tlx, tly}, - top_right: {_trx, ytr} - } - } - } = - Lead.set_spines(figure) - - assert Float.round(tly, 10) == - Float.round(frame_height - (title_font_size / 150 + 10 / 96), 10) - - # the offset of the title - assert Float.round(ytr, 10) == - Float.round(frame_height - (title_font_size / 150 + 10 / 96), 10) - end - - test "sets coordinates by reducing xlabel", %{figure: figure} do - margin = 0 - font_size = 16 - title_font_size = 0 - - figure = - figure - |> Matplotex.figure(%{margin: margin}) - |> Matplotex.set_rc_params( - x_tick_font_size: 0, - y_tick_font_size: 0, - title_font_size: title_font_size, - x_label_font_size: font_size, - y_label_font_size: 0 - ) - - assert %Figure{ - axes: %LinePlot{ - coords: %Coords{ - bottom_left: {_blx, bly}, - bottom_right: {_brx, bry} - } - } - } = - Lead.set_spines(figure) - - assert Float.round(bly, 10) == - Float.round(font_size / 150 + @padding_and_tick_line_space, 10) - - assert Float.round(bry, 10) == - Float.round(font_size / 150 + @padding_and_tick_line_space, 10) - end - - test "sets coordinates by reducing ylabel", %{figure: figure} do - margin = 0 - font_size = 16 - title_font_size = 0 - - figure = - figure - |> Matplotex.figure(%{margin: margin}) - |> Matplotex.set_rc_params( - x_tick_font_size: 0, - y_tick_font_size: 0, - title_font_size: title_font_size, - x_label_font_size: 0, - y_label_font_size: font_size - ) - - assert %Figure{ - axes: %LinePlot{ - coords: %Coords{ - bottom_left: {blx, _bly}, - top_left: {tlx, _tly} - } - } - } = - Lead.set_spines(figure) - - assert Float.round(tlx, 10) == - Float.round(font_size / 150 + @padding_and_tick_line_space, 10) - - assert Float.round(blx, 10) == - Float.round(font_size / 150 + @padding_and_tick_line_space, 10) - end - - test "set coordinates by reducing xticks", %{figure: figure} do - margin = 0 - font_size = 16 - title_font_size = 0 - - figure = - figure - |> Matplotex.figure(%{margin: margin}) - |> Matplotex.set_rc_params( - x_tick_font_size: font_size, - y_tick_font_size: 0, - title_font_size: title_font_size, - x_label_font_size: 0, - y_label_font_size: 0 - ) - - assert %Figure{ - axes: %LinePlot{ - coords: %Coords{ - bottom_left: {_blx, bly}, - bottom_right: {_brx, bry} - } - } - } = - Lead.set_spines(figure) - - assert bly == font_size / 150 + @padding_and_tick_line_space - assert bry == font_size / 150 + @padding_and_tick_line_space - end - - test "set coordinates by reducing yticks", %{figure: figure} do - margin = 0 - font_size = 16 - title_font_size = 0 - - figure = - figure - |> Matplotex.figure(%{margin: margin}) - |> Matplotex.set_rc_params( - x_tick_font_size: 0, - y_tick_font_size: font_size, - title_font_size: title_font_size, - x_label_font_size: 0, - y_label_font_size: 0 - ) - - assert %Figure{ - axes: %LinePlot{ - coords: %Coords{ - bottom_left: {blx, _bly}, - top_left: {tlx, _tly} - } - } - } = - Lead.set_spines(figure) - - assert tlx == font_size / 150 + @padding_and_tick_line_space - assert blx == font_size / 150 + @padding_and_tick_line_space - end - end - describe "set_regions/1" do test "sets region_xy", %{figure2: figure} do assert %Figure{ @@ -260,8 +37,7 @@ defmodule Matplotex.Figure.LeadTest do } } = Lead.set_regions(figure) - assert Enum.all?([rxx, rxwidth, rxheight, ryy, rywidth, ryheight], &(&1 > 0)) - assert Enum.all?([rxy, ryx], &(&1 == 0)) + assert Enum.all?([rxx, rxy, rxwidth, rxheight, ryx, ryy, rywidth, ryheight], &(&1 != 0)) end test "set region title updates the values for titles space", %{figure2: figure} do @@ -271,7 +47,7 @@ defmodule Matplotex.Figure.LeadTest do } } = Lead.set_regions(figure) - assert Enum.all?([rtx, rty, rtwidth, rtheight], &(&1 > 0)) + assert Enum.all?([rtx, rty, rtwidth, rtheight], &(&1 != 0)) end test "setting region legend", %{figure2: figure} do @@ -283,7 +59,7 @@ defmodule Matplotex.Figure.LeadTest do } } = Lead.set_regions(figure) - assert Enum.all?([rlx, rly, rlwidth, rlheight], &(&1 > 0)) + assert Enum.all?([rlx, rly, rlwidth, rlheight], &(&1 != 0)) end test "setting content takes the same width of x region and y region", %{figure2: figure} do @@ -300,5 +76,85 @@ defmodule Matplotex.Figure.LeadTest do assert rcwidth == rxwidth assert ryheight == rcheight end + + test "generates ticks from dataset if does't exist for show_ticks true", %{ + figure2: %Figure{axes: %{tick: tick} = axes} = figure + } do + figure = %Figure{ + figure + | axes: %{axes | tick: %TwoD{tick | x: nil, y: nil}, limit: %TwoD{x: nil, y: nil}} + } + + assert %Figure{ + figsize: {width, height}, + axes: %{ + tick: %TwoD{x: xticks, y: yticks}, + data: {x, y} + } + } = Lead.set_regions(figure) + + assert Enum.min(xticks) == 0 + assert Enum.min(yticks) == 0 + assert Enum.max(xticks) == Enum.max(x) + assert Enum.max(yticks) == Enum.max(y) + assert length(xticks) == ceil(width) + assert length(yticks) == ceil(height) + end + end + + test "all coordinates will be zero if input is zero", %{figure2: figure} do + figure = Matplotex.figure(figure, %{figsize: {0, 0}}) + + assert %Figure{ + axes: %{ + region_x: %Region{x: rxx, width: rxwidth}, + region_y: %Region{y: ryy, height: ryheight}, + region_title: %Region{x: rtx, y: rty, width: rtwidth, height: rtheight}, + region_content: %Region{x: rcx, y: rcy, width: rcwidth, height: rcheight} + } + } = Lead.set_regions(figure) + + assert rxx == 0 + assert ryy == 0 + assert rtx == 0 + assert rty == 0 + assert rtwidth == 0 + assert rtheight == 0 + assert rcx == 0 + assert rcy == 0 + assert rcwidth == 0 + assert rcheight == 0 + assert ryheight == 0 + assert rxwidth == 0 + end + + test "height will tally with all vertical components", %{figure: figure} do + assert %Figure{ + figsize: {_width, height}, + margin: margin, + axes: %{ + region_x: %Region{height: rxheight}, + region_title: %Region{height: rtheight}, + region_content: %Region{height: rcheight} + } + } = Lead.set_regions(figure) + + margin_two_side = height * margin * 2 + assert height == margin_two_side + rxheight + rtheight + rcheight + end + + test "width will tally with all horizontal components", %{figure: figure} do + assert %Figure{ + figsize: {width, _height}, + margin: margin, + axes: %{ + region_y: %Region{width: ry_width}, + region_content: %Region{width: rcwidth}, + region_legend: %Region{width: rlwidth} + } + } = Lead.set_regions(figure) + + two_side_margin = width * margin * 2 + assert width == two_side_margin + ry_width + rcwidth + rlwidth end end diff --git a/test/matplotex/figure/rc_param_test.exs b/test/matplotex/figure/rc_param_test.exs index 21890ee..d5c9034 100644 --- a/test/matplotex/figure/rc_param_test.exs +++ b/test/matplotex/figure/rc_param_test.exs @@ -23,22 +23,71 @@ defmodule Matplotex.Figure.RcParamTest do describe "update_with_font/2" do test "updates associated fonts with font_size" do font_size = 10 + font_family = "Updated family" + font_weight = "Updated weight" + font_style = "Updated style" + + pt_to_inch_ratio = 1 / 60 rc_params = %RcParams{} - font = %Font{font_size: font_size} + + font = %Font{ + font_size: font_size, + font_family: font_family, + font_style: font_style, + font_weight: font_weight, + pt_to_inch_ratio: pt_to_inch_ratio + } params = %{ x_label_font_size: font_size, + x_label_font_family: font_family, + x_label_font_style: font_style, + x_label_font_weight: font_weight, y_label_font_size: font_size, + y_label_font_family: font_family, + y_label_font_style: font_style, + y_label_font_weight: font_weight, x_tick_font_size: font_size, - y_tick_font_size: font_size + x_tick_font_family: font_family, + x_tick_font_style: font_style, + x_tick_font_weight: font_weight, + y_tick_font_size: font_size, + y_tick_font_family: font_family, + y_tick_font_style: font_style, + y_tick_font_weight: font_weight, + x_label_pt_to_inch_ratio: pt_to_inch_ratio, + y_label_pt_to_inch_ratio: pt_to_inch_ratio, + x_tick_pt_to_inch_ratio: pt_to_inch_ratio, + y_tick_pt_to_inch_ratio: pt_to_inch_ratio } assert %RcParams{ x_label_font: font, y_label_font: font, x_tick_font: font, - y_tick_font: font + y_tick_font: %Font{font | text_anchor: "start"} } == RcParams.update_with_font(rc_params, params) end end + + test "font_associated_keys will make keys for an elemenent" do + element = :y_label + keys = RcParams.font_associated_keys(element) + + assert keys |> Enum.sort() == + [ + :y_label_dominant_baseline, + :y_label_font_size, + :y_label_font_family, + :y_label_font_style, + :y_label_font_weight, + :y_label_fill, + :y_label_unit_of_measurement, + :y_label_pt_to_inch_ratio, + :y_label_rotation, + :y_label_flate, + :y_label_text_anchor + ] + |> Enum.sort() + end end diff --git a/test/support/plot_case.ex b/test/support/plot_case.ex index 345150e..86f7d65 100644 --- a/test/support/plot_case.ex +++ b/test/support/plot_case.ex @@ -4,8 +4,6 @@ defmodule Matplotex.PlotCase do using do quote do - alias Matplotex.Blueprint.Label - alias Matplotex.Blueprint.Line import Matplotex.PlotCase, only: [set_bar: 0] end end