From dcb97024618a3e27441b71e12e4629c5b8fb7dbe Mon Sep 17 00:00:00 2001 From: Mohammed Sadique Date: Tue, 24 Dec 2024 20:06:05 +0530 Subject: [PATCH 1/2] histo --- lib/matplotex.ex | 5 + lib/matplotex/element/rect.ex | 9 +- lib/matplotex/figure/areal.ex | 2 +- lib/matplotex/figure/areal/bar_chart.ex | 1 - lib/matplotex/figure/areal/histogram.ex | 126 ++++++++++++++++++ lib/matplotex/figure/areal/plot_options.ex | 5 - lib/matplotex/figure/dataset.ex | 4 + lib/matplotex/helpers.ex | 9 ++ .../matplotex/figure/areal/histogram_test.exs | 31 +++++ test/matplotex_test.exs | 16 +++ 10 files changed, 199 insertions(+), 9 deletions(-) create mode 100644 lib/matplotex/figure/areal/histogram.ex create mode 100644 test/matplotex/figure/areal/histogram_test.exs diff --git a/lib/matplotex.ex b/lib/matplotex.ex index 7d68157..287f306 100644 --- a/lib/matplotex.ex +++ b/lib/matplotex.ex @@ -2,6 +2,7 @@ defmodule Matplotex do @moduledoc """ Module to generate a graph. """ + alias Matplotex.Figure.Areal.Histogram alias Matplotex.InputError alias Matplotex.Figure.Radial.Pie alias Matplotex.Figure.Areal.Scatter @@ -83,6 +84,10 @@ defmodule Matplotex do |> LinePlot.create({x, y}, opts) end + def hist(data, bins, opts) do + Histogram.create(%Figure{axes: %Histogram{}}, {data, bins}, opts) + end + @doc """ Sets X and Y labels for the graph with given font details diff --git a/lib/matplotex/element/rect.ex b/lib/matplotex/element/rect.ex index a8212bb..c727a99 100644 --- a/lib/matplotex/element/rect.ex +++ b/lib/matplotex/element/rect.ex @@ -1,8 +1,9 @@ defmodule Matplotex.Element.Rect do alias Matplotex.Element - @default_stroke_width 1 - @default_stroke "rgba(0,0,0,0)" + @default_stroke_width 2 + @default_stroke "black" + @default_opacity 1.0 use Element @type t :: %__MODULE__{ @@ -22,6 +23,8 @@ defmodule Matplotex.Element.Rect do :color, :type, stroke: @default_stroke, + fill_opacity: @default_opacity, + stroke_opacity: @default_opacity, stroke_width: @default_stroke_width ] @@ -37,6 +40,8 @@ defmodule Matplotex.Element.Rect do width="#{get_width(rect)}" height="#{get_height(rect)}" stroke-width="#{rect.stroke_width}" + stroke-opacity="#{rect.stroke_opacity}" + fill-opacity="#{rect.fill_opacity}" filter=""> ) end diff --git a/lib/matplotex/figure/areal.ex b/lib/matplotex/figure/areal.ex index 42bc022..7a36471 100644 --- a/lib/matplotex/figure/areal.ex +++ b/lib/matplotex/figure/areal.ex @@ -4,8 +4,8 @@ defmodule Matplotex.Figure.Areal do alias Matplotex.Figure.TwoD @callback create(struct(), any(), keyword()) :: struct() @callback materialize(struct()) :: struct() - @callback plotify(number(), tuple(), number(), number(), list(), atom()) :: number() @callback with_legend_handle(struct(), struct()) :: struct() + @optional_callbacks with_legend_handle: 2 defmacro __using__(_) do quote do @behaviour Matplotex.Figure.Areal diff --git a/lib/matplotex/figure/areal/bar_chart.ex b/lib/matplotex/figure/areal/bar_chart.ex index 537e6f4..48673b8 100644 --- a/lib/matplotex/figure/areal/bar_chart.ex +++ b/lib/matplotex/figure/areal/bar_chart.ex @@ -91,7 +91,6 @@ defmodule Matplotex.Figure.Areal.BarChart do %Figure{figure | axes: %{axes | element: elements_with_bar}} end - @impl Areal def plotify(value, {minl, maxl}, axis_size, transition, _data, :x) do s = axis_size / (maxl - minl) value * s + transition - minl * s diff --git a/lib/matplotex/figure/areal/histogram.ex b/lib/matplotex/figure/areal/histogram.ex new file mode 100644 index 0000000..804a51b --- /dev/null +++ b/lib/matplotex/figure/areal/histogram.ex @@ -0,0 +1,126 @@ +defmodule Matplotex.Figure.Areal.Histogram do + alias Matplotex.Element.Rect + alias Matplotex.Figure.RcParams + alias Matplotex.Figure.Areal.PlotOptions + alias Matplotex.Figure.Dataset + alias Matplotex.Figure.Areal.Region + alias Matplotex.Figure + alias Matplotex.Figure.Areal + use Areal + + frame( + tick: %TwoD{}, + limit: %TwoD{}, + label: %TwoD{}, + region_x: %Region{}, + region_y: %Region{}, + region_title: %Region{}, + region_legend: %Region{}, + region_content: %Region{} + ) + + @impl Areal + def create(%Figure{axes: %__MODULE__{} = axes} = figure, {data, bins}, opts) do + {x, y} = bins_and_hists(data, bins) + + dataset = Dataset.cast(%Dataset{x: x, y: y}, opts) + + %Figure{figure | axes: %__MODULE__{axes | data: {x, y}, dataset: [dataset]}} + |> PlotOptions.set_options_in_figure(opts) + end + + @impl Areal + def materialize(figure) do + figure + |> sanitize() + |> __MODULE__.materialized_by_region() + |> materialize_hist() + end + + defp materialize_hist(%Figure{axes: %{dataset: data,limit: %TwoD{x: x_lim, y: y_lim}, region_content: %Region{x: x_region_content, y: y_region_content, width: width_region_content, height: height_region_content}, element: element}, rc_params: %RcParams{x_padding: x_padding, white_space: white_space}}) do + x_padding_value = width_region_content * x_padding + white_space + shrinked_width_region_content = width_region_content - x_padding_value * 2 + + hist_elements = + data + |>Enum.map(fn dataset -> + dataset + |> do_transform(x_lim, y_lim, shrinked_width_region_content, height_region_content, {x_region_content + x_padding_value, y_region_content}) + |> capture(abs(y_region_content), shrinked_width_region_content) + end) + |>List.flatten() + %Figure{axes: %{element: element ++ hist_elements}} + end + + defp capture(%Dataset{transformed: transformed} = dataset, bly, region_width) do + capture(transformed, [], dataset, bly, region_width) + end + + defp capture( + [{x, y} | to_capture], + captured, + %Dataset{ + color: color, + x: bins, + pos: pos_factor, + edge_color: edge_color, + alpha: alpha + } = dataset, + bly, + region_width + ) do + capture( + to_capture, + captured ++ + [ + %Rect{ + type: "figure.histogram", + x: bin_position(x, pos_factor), + y: y, + width: region_width / length(bins), + height: bly - y, + color: color, + stroke: edge_color, + fill_opacity: alpha, + stroke_opacity: alpha + } + ], + dataset, + bly, + region_width + ) + end + + defp capture([], captured, _dataset, _bly, _region_width), do: captured + + + defp bin_position(x, pos_factor) when pos_factor < 0 do + x + pos_factor + end + + defp bin_position(x, _pos_factor), do: x + defp bins_and_hists(data, bins) do + {data_min, data_max} = Enum.min_max(data) + + bins_dist = + data_min + |> Nx.linspace(data_max, n: bins) + |> Nx.to_list() + + {hists, _} = + Enum.map_reduce(bins_dist, data_min, fn bin, previous_bin -> + frequency = Enum.frequencies_by(data, fn point -> point < bin && point > previous_bin end) + {Map.get(frequency, true, 0), bin} + end) + + {bins_dist, hists} + end + + defp sanitize(%Figure{axes: %__MODULE__{data: {x, y}}= axes} = figure) do + {ymin, ymax} = Enum.min_max(y) + {xmin, xmax} = Enum.min_max(x) + {xmin, xmax} = {floor(xmin), ceil(xmax)} + + %Figure{figure | axes: %__MODULE__{axes | limit: %TwoD{y: {floor(ymin), ceil(ymax)}, x: xlim }}} + end +end diff --git a/lib/matplotex/figure/areal/plot_options.ex b/lib/matplotex/figure/areal/plot_options.ex index ef6e3d1..65ccbcb 100644 --- a/lib/matplotex/figure/areal/plot_options.ex +++ b/lib/matplotex/figure/areal/plot_options.ex @@ -117,14 +117,9 @@ defmodule Matplotex.Figure.Areal.PlotOptions do %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 %{ diff --git a/lib/matplotex/figure/dataset.ex b/lib/matplotex/figure/dataset.ex index 9114332..a1cb0ea 100644 --- a/lib/matplotex/figure/dataset.ex +++ b/lib/matplotex/figure/dataset.ex @@ -4,6 +4,8 @@ defmodule Matplotex.Figure.Dataset do @default_linestyle "_" @default_width 0.2 @default_marker_size 3.5 + @default_stroke "black" + @default_alpha 1.0 defstruct [ :label, @@ -13,6 +15,8 @@ defmodule Matplotex.Figure.Dataset do y: [], transformed: [], color: @default_color, + edge_color: @default_stroke, + alpha: @default_alpha, marker: @default_marker, linestyle: @default_linestyle, marker_size: @default_marker_size diff --git a/lib/matplotex/helpers.ex b/lib/matplotex/helpers.ex index b08dd0b..a0734e0 100644 --- a/lib/matplotex/helpers.ex +++ b/lib/matplotex/helpers.ex @@ -307,4 +307,13 @@ defmodule Matplotex.Helpers do |> Matplotex.show() |> copy() end + + def hist() do + values = Nx.Random.key(12) |> Nx.Random.normal(0, 1, shape: {1000}) |> elem(0) |> Nx.to_list() + bins = 30 + + Matplotex.hist(values, bins, x_label: "Value", y_label: "Frequency", title: "Histogram", color: "blue", edge_color: "black", alpha: 0.7) + |> Matplotex.show() + |> copy() + end end diff --git a/test/matplotex/figure/areal/histogram_test.exs b/test/matplotex/figure/areal/histogram_test.exs new file mode 100644 index 0000000..2a30ae9 --- /dev/null +++ b/test/matplotex/figure/areal/histogram_test.exs @@ -0,0 +1,31 @@ +defmodule Matplotex.Figure.Areal.HistogramTest do + use Matplotex.PlotCase + alias Matplotex.Figure.Areal.Histogram + + setup do + values = Nx.Random.key(12) |> Nx.Random.normal(0, 1, shape: {1000}) |> elem(0) |> Nx.to_list() + bins = 30 + + figure = + Matplotex.hist(values, bins, x_label: "Value", y_label: "Frequency", title: "Histogram") + + {:ok, %{figure: figure, bins: bins}} + end + + describe "materialyze/1" do + test "materialyze with elements", %{figure: figure, bins: bins} do + assert figure = Histogram.materialize(figure) + elements = figure.axes.element + assert title = elements |> Enum.filter(fn elem -> elem.type == "figure.title" end) |> hd + assert title.text == "Histogram" + assert x_label = elements |> Enum.filter(fn elem -> elem.type == "figure.x_label" end) |> hd + assert x_label.text == "Value" + assert y_label = elements |> Enum.filter(fn elem -> elem.type == "figure.y_label" end) |> hd + assert y_label.text == "Frequency" + x_ticks = elements |> Enum.filter(fn elem -> elem.type == "figure.x_tick" end) + y_ticks = elements |> Enum.filter(fn elem -> elem.type == "figure.y_tick" end) + histogram_elements = elements|>Enum.filter(fn elem -> elem.type == "figure.histogram" end) + assert length(histogram_elements) == bins + end + end +end diff --git a/test/matplotex_test.exs b/test/matplotex_test.exs index fbd051e..a30a611 100644 --- a/test/matplotex_test.exs +++ b/test/matplotex_test.exs @@ -1,4 +1,5 @@ defmodule MatplotexTest do + alias Matplotex.Figure.Dataset alias Matplotex.InputError use Matplotex.PlotCase alias Matplotex.Figure @@ -272,4 +273,19 @@ defmodule MatplotexTest do assert figure.rc_params.x_label_font.font_size == 10 assert figure.rc_params.y_label_font.font_size == 10 end + + describe "hist" do + test "creates a figure for histogram" do + values = + Nx.Random.key(12) |> Nx.Random.normal(0, 1, shape: {1000}) |> elem(0) |> Nx.to_list() + + bins = 30 + figure = Matplotex.hist(values, bins, x_label: "Value", y_label: "Frequency") + assert %Dataset{x: x, y: y} = hd(figure.axes.dataset) + assert length(x) == bins + assert length(y) == bins + assert figure.axes.label.x == "Value" + assert figure.axes.label.y == "Frequency" + end + end end From dd2253f28534ee2d91b7d36bfd4054bca29c9f7c Mon Sep 17 00:00:00 2001 From: Mohammed Sadique Date: Thu, 26 Dec 2024 17:22:31 +0530 Subject: [PATCH 2/2] some cleanups --- lib/matplotex/figure/areal.ex | 27 +-- lib/matplotex/figure/areal/bar_chart.ex | 10 - lib/matplotex/figure/areal/histogram.ex | 11 +- lib/matplotex/figure/areal/line_plot.ex | 6 - lib/matplotex/figure/areal/scatter.ex | 5 - lib/matplotex/figure/areal/ticker.ex | 3 + lib/matplotex/figure/cast.ex | 195 ------------------ lib/matplotex/figure/lead.ex | 5 +- lib/matplotex/figure/rc_params.ex | 4 +- lib/matplotex/helpers.ex | 2 +- .../matplotex/figure/areal/histogram_test.exs | 2 + 11 files changed, 26 insertions(+), 244 deletions(-) diff --git a/lib/matplotex/figure/areal.ex b/lib/matplotex/figure/areal.ex index 7a36471..adf0ce0 100644 --- a/lib/matplotex/figure/areal.ex +++ b/lib/matplotex/figure/areal.ex @@ -1,4 +1,5 @@ defmodule Matplotex.Figure.Areal do + alias Matplotex.Figure.Areal.Ticker alias Matplotex.Utils.Algebra alias Matplotex.Figure.Dataset alias Matplotex.Figure.TwoD @@ -79,8 +80,13 @@ defmodule Matplotex.Figure.Areal do |> update_tick(tick) end - def add_ticks(%__MODULE__{tick: tick, size: size} = axes, {key, {_min, _max} = lim}) do - {ticks, lim} = __MODULE__.generate_ticks(lim) + def add_ticks(%__MODULE__{tick: tick, size: {width, height}= size} = axes, {key, {_min, _max} = lim}) do + number_of_ticks = if key == :x do + ceil(width) + else + ceil(height) + end + {ticks, lim} = Ticker.generate_ticks(lim, number_of_ticks ) tick = Map.put(tick, key, ticks) @@ -116,23 +122,6 @@ defmodule Matplotex.Figure.Areal do %{axes | legend: legend} end - def generate_xticks(%module{data: {x, _y}, tick: tick, limit: limit} = axes) do - {xticks, xlim} = - module.generate_ticks(x) - - tick = Map.put(tick, :x, xticks) - limit = update_limit(limit, :x, xlim) - %__MODULE__{axes | tick: tick, limit: limit} - end - - def generate_yticks(%module{data: {_x, y}, tick: tick, limit: limit} = axes) do - {yticks, ylim} = - module.generate_ticks(y) - - tick = Map.put(tick, :y, yticks) - limit = update_limit(limit, :y, ylim) - %__MODULE__{axes | tick: tick, limit: limit} - end defp update_limit(%TwoD{x: nil} = limit, :x, xlim) do %TwoD{limit | x: xlim} diff --git a/lib/matplotex/figure/areal/bar_chart.ex b/lib/matplotex/figure/areal/bar_chart.ex index 48673b8..d697e7c 100644 --- a/lib/matplotex/figure/areal/bar_chart.ex +++ b/lib/matplotex/figure/areal/bar_chart.ex @@ -91,16 +91,6 @@ defmodule Matplotex.Figure.Areal.BarChart do %Figure{figure | axes: %{axes | element: elements_with_bar}} end - def plotify(value, {minl, maxl}, axis_size, transition, _data, :x) do - s = axis_size / (maxl - minl) - value * s + transition - minl * s - end - - def plotify(value, {minl, maxl}, axis_size, transition, _data, :y) do - s = axis_size / (maxl - minl) - value * s + transition - minl * s - end - @impl Areal def with_legend_handle( %Legend{x: x, y: y, color: color, width: width, height: height} = legend, diff --git a/lib/matplotex/figure/areal/histogram.ex b/lib/matplotex/figure/areal/histogram.ex index 804a51b..64c342e 100644 --- a/lib/matplotex/figure/areal/histogram.ex +++ b/lib/matplotex/figure/areal/histogram.ex @@ -8,10 +8,13 @@ defmodule Matplotex.Figure.Areal.Histogram do alias Matplotex.Figure.Areal use Areal + @make_it_zero 0 + frame( tick: %TwoD{}, limit: %TwoD{}, label: %TwoD{}, + scale: %TwoD{}, region_x: %Region{}, region_y: %Region{}, region_title: %Region{}, @@ -20,12 +23,11 @@ defmodule Matplotex.Figure.Areal.Histogram do ) @impl Areal - def create(%Figure{axes: %__MODULE__{} = axes} = figure, {data, bins}, opts) do + def create(%Figure{axes: %__MODULE__{} = axes, rc_params: rc_params} = figure, {data, bins}, opts) do {x, y} = bins_and_hists(data, bins) dataset = Dataset.cast(%Dataset{x: x, y: y}, opts) - - %Figure{figure | axes: %__MODULE__{axes | data: {x, y}, dataset: [dataset]}} + %Figure{figure | axes: %__MODULE__{axes | data: {x, y}, dataset: [dataset]}, rc_params: %RcParams{rc_params | y_padding: @make_it_zero}} |> PlotOptions.set_options_in_figure(opts) end @@ -119,8 +121,7 @@ defmodule Matplotex.Figure.Areal.Histogram do defp sanitize(%Figure{axes: %__MODULE__{data: {x, y}}= axes} = figure) do {ymin, ymax} = Enum.min_max(y) {xmin, xmax} = Enum.min_max(x) - {xmin, xmax} = {floor(xmin), ceil(xmax)} - %Figure{figure | axes: %__MODULE__{axes | limit: %TwoD{y: {floor(ymin), ceil(ymax)}, x: xlim }}} + %Figure{figure | axes: %__MODULE__{axes | limit: %TwoD{x: {floor(xmin), ceil(xmax)},y: {floor(ymin), ceil(ymax)}}}} end end diff --git a/lib/matplotex/figure/areal/line_plot.ex b/lib/matplotex/figure/areal/line_plot.ex index fd24fa4..10ec56f 100644 --- a/lib/matplotex/figure/areal/line_plot.ex +++ b/lib/matplotex/figure/areal/line_plot.ex @@ -93,12 +93,6 @@ defmodule Matplotex.Figure.Areal.LinePlot do %Figure{figure | axes: %{axes | element: elements}} end - @impl Areal - def plotify(value, {minl, maxl}, axis_size, transition, _, _) do - s = axis_size / (maxl - minl) - value * s + transition - minl * s - end - @impl Areal def with_legend_handle( %Legend{x: x, y: y, color: color, width: marker_size} = legend, diff --git a/lib/matplotex/figure/areal/scatter.ex b/lib/matplotex/figure/areal/scatter.ex index ec6525c..3d43d80 100644 --- a/lib/matplotex/figure/areal/scatter.ex +++ b/lib/matplotex/figure/areal/scatter.ex @@ -132,11 +132,6 @@ defmodule Matplotex.Figure.Areal.Scatter do end defp capture(_, captured, _), do: captured - @impl Areal - def plotify(value, {minl, maxl}, axis_size, transition, _, _) do - s = axis_size / (maxl - minl) - value * s + transition - minl * s - end @impl Areal def with_legend_handle( diff --git a/lib/matplotex/figure/areal/ticker.ex b/lib/matplotex/figure/areal/ticker.ex index 1df1d4c..34bd420 100644 --- a/lib/matplotex/figure/areal/ticker.ex +++ b/lib/matplotex/figure/areal/ticker.ex @@ -4,6 +4,9 @@ defmodule Matplotex.Figure.Areal.Ticker do step = (max - min) / @tick_in_plot produce_ticks(min, max, step, [format_number(min)]) end + def generate_ticks({lower_limit, upper_limit} = lim, number_of_ticks) do + {lower_limit |> Nx.linspace(upper_limit, n: number_of_ticks) |> Nx.to_list(), lim} + end defp produce_ticks(value, max, _step, ticks) when value >= max do Enum.reverse(ticks) diff --git a/lib/matplotex/figure/cast.ex b/lib/matplotex/figure/cast.ex index a6057f7..9760562 100644 --- a/lib/matplotex/figure/cast.ex +++ b/lib/matplotex/figure/cast.ex @@ -12,7 +12,6 @@ defmodule Matplotex.Figure.Cast do alias Matplotex.Figure.Coords alias Matplotex.Figure alias Matplotex.Figure.Dataset - @tickline_offset 5 / 96 @xtick_type "figure.x_tick" @ytick_type "figure.y_tick" @stroke_grid "#ddd" @@ -326,85 +325,6 @@ defmodule Matplotex.Figure.Cast do %Figure{figure | axes: %{axes | element: element}} end - def cast_xticks( - %Figure{ - axes: - %module{ - tick: %{x: x_ticks}, - limit: %{x: {_min, _max} = xlim}, - size: {width, _height}, - data: {x_data, y_data}, - dataset: dataset, - element: elements, - show_x_ticks: true, - coords: %Coords{bottom_left: {blx, bly}, x_ticks: {_xtx, xty}} = coords - } = axes, - rc_params: %RcParams{x_tick_font: tick_font, x_padding: x_padding} - } = figure - ) - when is_list(x_ticks) do - # TODO: Only if it has to confine - x_ticks = confine_ticks(x_ticks, xlim) - x_data = confine_data(x_data, xlim) - dataset = confine_data(dataset, xlim, :x) - - {xtick_elements, vgridxs} = - Enum.map(x_ticks, fn tick -> - {tick_position, label} = - plotify_tick( - module, - tick, - xlim, - width - width * x_padding * 2, - blx + width * x_padding, - x_data, - :x - ) - - label = - %Label{ - type: @xtick_type, - x: tick_position, - y: xty, - text: label - } - |> merge_structs(tick_font) - - # TODO: find a mechanism to pass custom font for ticks - line = %Line{ - type: @xtick_type, - x1: tick_position, - y1: bly, - x2: tick_position, - y2: bly - @tickline_offset - } - - {%Tick{type: @xtick_type, tick_line: line, label: label}, tick_position} - end) - |> Enum.unzip() - - elements = elements ++ xtick_elements - vgrids = Enum.map(vgridxs, fn g -> {g, bly} end) - - %Figure{ - figure - | axes: %{ - axes - | data: {x_data, y_data}, - dataset: dataset, - element: elements, - coords: %{coords | vgrids: vgrids} - } - } - end - - def cast_xticks(%Figure{axes: %{tick: %{x: _}, limit: %{x: nil}, show_x_ticks: true}} = figure) do - figure - |> set_xlim_from_ticks() - |> cast_xticks() - end - - def cast_xticks(%Figure{} = figure), do: figure def cast_xticks_by_region( %Figure{ @@ -507,86 +427,6 @@ defmodule Matplotex.Figure.Cast 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: - %module{ - tick: %{y: y_ticks}, - size: {_width, height}, - element: elements, - coords: %Coords{bottom_left: {blx, bly}, y_ticks: {ytx, _yty}} = coords, - limit: %{y: {_min, _max} = ylim}, - data: {x_data, y_data}, - dataset: dataset, - show_y_ticks: true - } = axes, - rc_params: %RcParams{y_tick_font: tick_font, y_padding: padding} - } = figure - ) do - y_ticks = confine_ticks(y_ticks, ylim) - y_data = confine_data(y_data, ylim) - dataset = confine_data(dataset, ylim, :y) - - {ytick_elements, hgridys} = - Enum.map(y_ticks, fn tick -> - {tick_position, label} = - plotify_tick( - module, - tick, - ylim, - height - height * padding * 2, - bly + height * padding, - y_data, - :y - ) - - label = - %Label{ - type: @ytick_type, - y: tick_position, - x: ytx, - text: label, - text_anchor: "end", - dominant_baseline: "middle" - } - |> merge_structs(tick_font) - - # TODO: find a mechanism to pass custom font for ticks - line = %Line{ - type: @ytick_type, - y1: tick_position, - x1: blx, - x2: blx - @tickline_offset, - y2: tick_position - } - - {%Tick{type: @ytick_type, tick_line: line, label: label}, tick_position} - end) - |> Enum.unzip() - - elements = elements ++ ytick_elements - hgrids = Enum.map(hgridys, fn g -> {blx, g} end) - - %Figure{ - figure - | axes: %{ - axes - | data: {x_data, y_data}, - dataset: dataset, - element: elements, - coords: %{coords | hgrids: hgrids} - } - } - end - - def cast_yticks(%Figure{axes: %{tick: %{y: _}, limit: %{y: nil}, show_y_ticks: true}} = figure) do - figure - |> set_ylim_from_ticks() - |> cast_yticks() - end - - def cast_ytick(%Figure{} = figure), do: figure def cast_yticks_by_region( %Figure{ @@ -853,23 +693,6 @@ defmodule Matplotex.Figure.Cast do def cast_legends(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 - - defp plotify_tick(module, value, lim, axis_size, transition, data, axis) do - {module.plotify(value, lim, axis_size, transition, data, axis), value} - end - - defp min_max([{_pos, _label} | _] = ticks) do - ticks - |> Enum.min_max_by(fn {_labe, pos} -> pos end) - |> then(fn {{_label_min, pos_min}, {_label_max, pos_max}} -> {pos_min, pos_max} end) - end - - defp min_max(ticks) do - Enum.min_max(ticks) - end defp calculate_center(%Coords{bottom_left: bottom_left, bottom_right: bottom_right}, {x, y}, :x) do {calculate_distance(bottom_left, bottom_right) / 2 + x, y} @@ -899,24 +722,6 @@ defmodule Matplotex.Figure.Cast do defp rotate_label(:y), do: -90 - defp set_xlim_from_ticks(%Figure{axes: %module{tick: %{x: xtick}} = axes} = figure) do - {xmin, xmax} = min_max(xtick) - - xscale = xmax |> round() |> div(length(xtick) - 1) - - xlim = {round(xmin - xscale), round(xmax + xscale)} - axes = module.set_limit(axes, {:x, xlim}) - - %Figure{figure | axes: axes} - end - - defp set_ylim_from_ticks(%Figure{axes: %module{tick: %{y: ytick}} = axes} = figure) do - {ymin, ymax} = min_max(ytick) - yscale = ymax |> round() |> div(length(ytick) - 1) - ylim = {round(ymin - yscale), round(ymax + yscale)} - axes = module.set_limit(axes, {:y, ylim}) - %Figure{figure | axes: axes} - end defp confine_ticks([{_l, _v} | _] = ticks, {min, max}) do ticks diff --git a/lib/matplotex/figure/lead.ex b/lib/matplotex/figure/lead.ex index 424067a..672b9b6 100644 --- a/lib/matplotex/figure/lead.ex +++ b/lib/matplotex/figure/lead.ex @@ -56,6 +56,7 @@ defmodule Matplotex.Figure.Lead do defp ensure_ticks_are_valid( %Figure{ figsize: {width, height}, + rc_params: %RcParams{x_ticks_count: x_ticks_count, y_ticks_count: y_ticks_count}, axes: %{ data: {x_data, y_data}, @@ -64,8 +65,8 @@ defmodule Matplotex.Figure.Lead do } = 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) + {x_ticks, x_lim} = maybe_generate_ticks(x_ticks, x_lim, x_data, x_ticks_count || width) + {y_ticks, y_lim} = maybe_generate_ticks(y_ticks, y_lim, y_data, y_ticks_count || height) %Figure{ figure diff --git a/lib/matplotex/figure/rc_params.ex b/lib/matplotex/figure/rc_params.ex index 2c7291a..96ef24f 100644 --- a/lib/matplotex/figure/rc_params.ex +++ b/lib/matplotex/figure/rc_params.ex @@ -48,7 +48,9 @@ defmodule Matplotex.Figure.RcParams do white_space: 0, label_padding: @label_padding, legend_width: @default_legend_width_percentage, - legend_items_orientation: @default_legend_items_orientation + legend_items_orientation: @default_legend_items_orientation, + x_ticks_count: nil, + y_ticks_count: nil def get_rc(%__MODULE__{} = rc_param, get_func) do apply(__MODULE__, get_func, [rc_param]) diff --git a/lib/matplotex/helpers.ex b/lib/matplotex/helpers.ex index a0734e0..61284bc 100644 --- a/lib/matplotex/helpers.ex +++ b/lib/matplotex/helpers.ex @@ -312,7 +312,7 @@ defmodule Matplotex.Helpers do values = Nx.Random.key(12) |> Nx.Random.normal(0, 1, shape: {1000}) |> elem(0) |> Nx.to_list() bins = 30 - Matplotex.hist(values, bins, x_label: "Value", y_label: "Frequency", title: "Histogram", color: "blue", edge_color: "black", alpha: 0.7) + Matplotex.hist(values, bins, x_label: "Value", y_label: "Frequency", title: "Histogram", color: "blue", edge_color: "black", alpha: 0.7, x_ticks_count: 9) |> Matplotex.show() |> copy() end diff --git a/test/matplotex/figure/areal/histogram_test.exs b/test/matplotex/figure/areal/histogram_test.exs index 2a30ae9..5198851 100644 --- a/test/matplotex/figure/areal/histogram_test.exs +++ b/test/matplotex/figure/areal/histogram_test.exs @@ -26,6 +26,8 @@ defmodule Matplotex.Figure.Areal.HistogramTest do y_ticks = elements |> Enum.filter(fn elem -> elem.type == "figure.y_tick" end) histogram_elements = elements|>Enum.filter(fn elem -> elem.type == "figure.histogram" end) assert length(histogram_elements) == bins + assert length(x_ticks) > 3 + assert length(y_ticks) > 3 end end end